2 * Copyright © 2017-2021 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/>.
20 package com.stoutner.privacybrowser.dialogs
22 import android.annotation.SuppressLint
23 import android.app.Dialog
24 import android.content.DialogInterface
25 import android.content.res.Configuration
26 import android.os.Bundle
27 import android.text.SpannableStringBuilder
28 import android.text.Spanned
29 import android.text.style.ForegroundColorSpan
30 import android.view.KeyEvent
31 import android.view.View
32 import android.view.WindowManager
33 import android.webkit.HttpAuthHandler
34 import android.widget.EditText
35 import android.widget.TextView
37 import androidx.appcompat.app.AlertDialog
38 import androidx.fragment.app.DialogFragment
39 import androidx.preference.PreferenceManager
41 import com.stoutner.privacybrowser.R
42 import com.stoutner.privacybrowser.activities.MainWebViewActivity
43 import com.stoutner.privacybrowser.views.NestedScrollWebView
45 // Define the class constants.
46 private const val HOST = "host"
47 private const val REALM = "realm"
48 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
50 class HttpAuthenticationDialog : DialogFragment() {
51 // Define the class variables.
52 private var dismissDialog: Boolean = false
54 // Declare the class views.
55 private lateinit var usernameEditText: EditText
56 private lateinit var passwordEditText: EditText
59 // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
61 fun displayDialog(host: String, realm: String, webViewFragmentId: Long): HttpAuthenticationDialog {
62 // Create an arguments bundle.
63 val argumentsBundle = Bundle()
65 // Store the variables in the bundle.
66 argumentsBundle.putString(HOST, host)
67 argumentsBundle.putString(REALM, realm)
68 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
70 // Create a new instance of the HTTP authentication dialog.
71 val httpAuthenticationDialog = HttpAuthenticationDialog()
73 // Add the arguments bundle to the dialog.
74 httpAuthenticationDialog.arguments = argumentsBundle
76 // Return the new dialog.
77 return httpAuthenticationDialog
81 // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
82 @SuppressLint("InflateParams")
83 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
84 // Get a handle for the arguments.
85 val arguments = requireArguments()
87 // Get the variables from the bundle.
88 val httpAuthHost = arguments.getString(HOST)
89 val httpAuthRealm = arguments.getString(REALM)
90 val webViewFragmentId = arguments.getLong(WEBVIEW_FRAGMENT_ID)
92 // Try to populate the alert dialog.
93 try { // Getting the WebView tab fragment will fail if Privacy Browser has been restarted.
94 // Get the current position of this WebView fragment.
95 val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
97 // Get the WebView tab fragment.
98 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
100 // Get the fragment view.
101 val fragmentView = webViewTabFragment.requireView()
103 // Get a handle for the current WebView.
104 val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
106 // Get a handle for the HTTP authentication handler.
107 val httpAuthHandler = nestedScrollWebView.httpAuthHandler
109 // Use an alert dialog builder to create the alert dialog.
110 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
112 // Set the icon according to the theme.
113 dialogBuilder.setIconAttribute(R.attr.lockBlueIcon)
116 dialogBuilder.setTitle(R.string.http_authentication)
118 // Set the view. The parent view is `null` because it will be assigned by the alert dialog.
119 dialogBuilder.setView(layoutInflater.inflate(R.layout.http_authentication_dialog, null))
121 // Set the close button listener.
122 dialogBuilder.setNegativeButton(R.string.close) { _: DialogInterface?, _: Int ->
123 if (httpAuthHandler != null) {
124 // Cancel the HTTP authentication request.
125 httpAuthHandler.cancel()
127 // Reset the HTTP authentication handler.
128 nestedScrollWebView.resetHttpAuthHandler()
132 // Set the proceed button listener.
133 dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
134 // Send the login information
135 login(httpAuthHandler, nestedScrollWebView)
138 // Create an alert dialog from the alert dialog builder.
139 val alertDialog = dialogBuilder.create()
141 // Get the alert dialog window.
142 val dialogWindow = alertDialog.window!!
144 // Get a handle for the shared preferences.
145 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
147 // Get the screenshot preference.
148 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
150 // Disable screenshots if not allowed.
151 if (!allowScreenshots) {
152 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
155 // Display the keyboard.
156 dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
158 // The alert dialog needs to be shown before the contents can be modified.
161 // Get handles for the views.
162 val realmTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_realm)!!
163 val hostTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_host)!!
164 usernameEditText = alertDialog.findViewById(R.id.http_authentication_username)!!
165 passwordEditText = alertDialog.findViewById(R.id.http_authentication_password)!!
167 // Set the realm text.
168 realmTextView.text = httpAuthRealm
170 // Initialize the host label and the spannable string builder.
171 val hostLabel = getString(R.string.host) + " "
172 val hostStringBuilder = SpannableStringBuilder(hostLabel + httpAuthHost)
174 // Get the current theme status.
175 val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
177 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until API >= 23.
178 val blueColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
179 @Suppress("DEPRECATION")
180 ForegroundColorSpan(resources.getColor(R.color.blue_700))
182 @Suppress("DEPRECATION")
183 ForegroundColorSpan(resources.getColor(R.color.violet_700))
186 // Setup the span to display the host name in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
187 hostStringBuilder.setSpan(blueColorSpan, hostLabel.length, hostStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
189 // Set the host text.
190 hostTextView.text = hostStringBuilder
192 // Allow the enter key on the keyboard to send the login information from the username edit text.
193 usernameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
194 // Check the key code and event.
195 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) { // The enter key was pressed.
196 // Send the login information.
197 login(httpAuthHandler, nestedScrollWebView)
199 // Manually dismiss the alert dialog.
200 alertDialog.dismiss()
202 // Consume the event.
203 return@setOnKeyListener true
204 } else { // If any other key was pressed, do not consume the event.
205 return@setOnKeyListener false
209 // Allow the enter key on the keyboard to send the login information from the password edit text.
210 passwordEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
211 // Check the key code and event.
212 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) { // The enter key was pressed.
213 // Send the login information.
214 login(httpAuthHandler, nestedScrollWebView)
216 // Manually dismiss the alert dialog.
217 alertDialog.dismiss()
219 // Consume the event.
220 return@setOnKeyListener true
221 } else { // If any other key was pressed, do not consume the event.
222 return@setOnKeyListener false
226 // Return the alert dialog.
228 } catch (exception: Exception) { // Privacy Browser was restarted and the HTTP auth handler no longer exists.
229 // Use an alert dialog builder to create an empty alert dialog.
230 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
232 // Create an empty alert dialog from the alert dialog builder.
233 val alertDialog = dialogBuilder.create()
235 // Set the flag to dismiss the dialog as soon as it is resumed.
238 // Return the alert dialog.
243 override fun onResume() {
244 // Run the default commands.
247 // Dismiss the alert dialog if the activity was restarted and the HTTP auth handler no longer exists.
253 private fun login(httpAuthHandler: HttpAuthHandler?, nestedScrollWebView: NestedScrollWebView) {
254 if (httpAuthHandler != null) {
255 // Send the login information.
256 httpAuthHandler.proceed(usernameEditText.text.toString(), passwordEditText.text.toString())
258 // Reset the HTTP authentication handler.
259 nestedScrollWebView.resetHttpAuthHandler()