2 * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
19 package com.stoutner.privacybrowser.dialogs
21 import android.annotation.SuppressLint
22 import android.app.Dialog
23 import android.content.DialogInterface
24 import android.content.res.Configuration
25 import android.os.Bundle
26 import android.text.SpannableStringBuilder
27 import android.text.Spanned
28 import android.text.style.ForegroundColorSpan
29 import android.view.KeyEvent
30 import android.view.View
31 import android.view.WindowManager
32 import android.webkit.HttpAuthHandler
33 import android.widget.EditText
34 import android.widget.TextView
36 import androidx.appcompat.app.AlertDialog
37 import androidx.fragment.app.DialogFragment
38 import androidx.preference.PreferenceManager
40 import com.stoutner.privacybrowser.R
41 import com.stoutner.privacybrowser.activities.MainWebViewActivity
42 import com.stoutner.privacybrowser.views.NestedScrollWebView
44 // Declare the class constants.
45 private const val HOST = "host"
46 private const val REALM = "realm"
47 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
49 class HttpAuthenticationDialog: DialogFragment() {
50 // Define the class variables.
51 private var dismissDialog: Boolean = false
53 // Define the class views.
54 private lateinit var usernameEditText: EditText
55 private lateinit var passwordEditText: EditText
58 // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. Also, the function can then be moved out of a companion object and just become a package-level function.
60 fun displayDialog(host: String, realm: String, webViewFragmentId: Long): HttpAuthenticationDialog {
61 // Create an arguments bundle.
62 val argumentsBundle = Bundle()
64 // Store the variables in the bundle.
65 argumentsBundle.putString(HOST, host)
66 argumentsBundle.putString(REALM, realm)
67 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
69 // Create a new instance of the HTTP authentication dialog.
70 val httpAuthenticationDialog = HttpAuthenticationDialog()
72 // Add the arguments bundle to the dialog.
73 httpAuthenticationDialog.arguments = argumentsBundle
75 // Return the new dialog.
76 return httpAuthenticationDialog
80 // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
81 @SuppressLint("InflateParams")
82 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
83 // Get a handle for the arguments.
84 val arguments = requireArguments()
86 // Get the variables from the bundle.
87 val httpAuthHost = arguments.getString(HOST)
88 val httpAuthRealm = arguments.getString(REALM)
89 val webViewFragmentId = arguments.getLong(WEBVIEW_FRAGMENT_ID)
91 // Try to populate the alert dialog.
92 try { // Getting the WebView tab fragment will fail if Privacy Browser has been restarted.
93 // Get the current position of this WebView fragment.
94 val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
96 // Get the WebView tab fragment.
97 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
99 // Get the fragment view.
100 val fragmentView = webViewTabFragment.requireView()
102 // Get a handle for the current WebView.
103 val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
105 // Get a handle for the HTTP authentication handler.
106 val httpAuthHandler = nestedScrollWebView.httpAuthHandler
108 // Use an alert dialog builder to create the alert dialog.
109 val dialogBuilder = AlertDialog.Builder(requireActivity(), R.style.PrivacyBrowserAlertDialog)
111 // Get the current theme status.
112 val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
114 // Set the icon according to the theme.
115 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
116 dialogBuilder.setIcon(R.drawable.lock_day)
119 dialogBuilder.setIcon(R.drawable.lock_night)
123 dialogBuilder.setTitle(R.string.http_authentication)
125 // Get the activity's layout inflater.
126 val layoutInflater = requireActivity().layoutInflater
128 // Set the layout. The parent view is `null` because it will be assigned by the alert dialog.
129 dialogBuilder.setView(layoutInflater.inflate(R.layout.http_authentication_dialog, null))
131 // Set the close button listener.
132 dialogBuilder.setNegativeButton(R.string.close) { _: DialogInterface?, _: Int ->
133 // Cancel the HTTP authentication request.
134 httpAuthHandler.cancel()
136 // Reset the HTTP authentication handler.
137 nestedScrollWebView.resetHttpAuthHandler()
138 }// Set the proceed button listener.
139 dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
140 // Send the login information
141 login(httpAuthHandler)
143 // Reset the HTTP authentication handler.
144 nestedScrollWebView.resetHttpAuthHandler()
147 // Create an alert dialog from the alert dialog builder.
148 val alertDialog = dialogBuilder.create()
150 // Get the alert dialog window.
151 val dialogWindow = alertDialog.window!!
153 // Get a handle for the shared preferences.
154 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
156 // Get the screenshot preference.
157 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
159 // Disable screenshots if not allowed.
160 if (!allowScreenshots) {
161 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
164 // Display the keyboard.
165 dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
167 // The alert dialog needs to be shown before the contents can be modified.
170 // Get handles for the views.
171 val realmTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_realm)!!
172 val hostTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_host)!!
173 usernameEditText = alertDialog.findViewById(R.id.http_authentication_username)!!
174 passwordEditText = alertDialog.findViewById(R.id.http_authentication_password)!!
176 // Set the realm text.
177 realmTextView.text = httpAuthRealm
179 // Initialize the host label and the spannable string builder.
180 val hostLabel = getString(R.string.host) + " "
181 val hostStringBuilder = SpannableStringBuilder(hostLabel + httpAuthHost)
183 // Create a blue foreground color span.
184 val blueColorSpan: ForegroundColorSpan
186 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until API >= 23.
187 blueColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
188 @Suppress("DEPRECATION")
189 ForegroundColorSpan(resources.getColor(R.color.blue_700))
191 @Suppress("DEPRECATION")
192 ForegroundColorSpan(resources.getColor(R.color.violet_500))
195 // Setup the span to display the host name in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
196 hostStringBuilder.setSpan(blueColorSpan, hostLabel.length, hostStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
198 // Set the host text.
199 hostTextView.text = hostStringBuilder
201 // Allow the enter key on the keyboard to send the login information from the username edit text.
202 usernameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
203 // Check the key code and event.
204 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) { // The enter key was pressed.
205 // Send the login information.
206 login(httpAuthHandler)
208 // Manually dismiss the alert dialog.
209 alertDialog.dismiss()
211 // Consume the event.
212 return@setOnKeyListener true
213 } else { // If any other key was pressed, do not consume the event.
214 return@setOnKeyListener false
218 // Allow the enter key on the keyboard to send the login information from the password edit text.
219 passwordEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
220 // Check the key code and event.
221 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) { // The enter key was pressed.
222 // Send the login information.
223 login(httpAuthHandler)
225 // Manually dismiss the alert dialog.
226 alertDialog.dismiss()
228 // Consume the event.
229 return@setOnKeyListener true
230 } else { // If any other key was pressed, do not consume the event.
231 return@setOnKeyListener false
235 // Return the alert dialog.
237 } catch (exception: Exception) { // Privacy Browser was restarted and the HTTP auth handler no longer exists.
238 // Use an alert dialog builder to create an empty alert dialog.
239 val dialogBuilder = AlertDialog.Builder(requireActivity(), R.style.PrivacyBrowserAlertDialog)
241 // Create an empty alert dialog from the alert dialog builder.
242 val alertDialog = dialogBuilder.create()
244 // Set the flag to dismiss the dialog as soon as it is resumed.
247 // Return the alert dialog.
252 override fun onResume() {
253 // Run the default command.
256 // Dismiss the alert dialog if the activity was restarted and the HTTP auth handler no longer exists.
262 private fun login(httpAuthHandler: HttpAuthHandler) {
263 // Send the login information.
264 httpAuthHandler.proceed(usernameEditText.text.toString(), passwordEditText.text.toString())