2 * Copyright © 2017-2022 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
6 * Privacy Browser Android 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 Android 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 Android. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.dialogs
22 import android.app.Dialog
23 import android.content.DialogInterface
24 import android.os.Bundle
25 import android.text.SpannableStringBuilder
26 import android.text.Spanned
27 import android.text.style.ForegroundColorSpan
28 import android.view.KeyEvent
29 import android.view.View
30 import android.view.WindowManager
31 import android.webkit.HttpAuthHandler
32 import android.widget.EditText
33 import android.widget.TextView
35 import androidx.appcompat.app.AlertDialog
36 import androidx.fragment.app.DialogFragment
37 import androidx.preference.PreferenceManager
39 import com.stoutner.privacybrowser.R
40 import com.stoutner.privacybrowser.activities.MainWebViewActivity
41 import com.stoutner.privacybrowser.views.NestedScrollWebView
43 // Define the class constants.
44 private const val HOST = "host"
45 private const val REALM = "realm"
46 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
48 class HttpAuthenticationDialog : DialogFragment() {
49 // Define the class variables.
50 private var dismissDialog: Boolean = false
52 // Declare the class views.
53 private lateinit var usernameEditText: EditText
54 private lateinit var passwordEditText: EditText
57 // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
59 fun displayDialog(host: String, realm: String, webViewFragmentId: Long): HttpAuthenticationDialog {
60 // Create an arguments bundle.
61 val argumentsBundle = Bundle()
63 // Store the variables in the bundle.
64 argumentsBundle.putString(HOST, host)
65 argumentsBundle.putString(REALM, realm)
66 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
68 // Create a new instance of the HTTP authentication dialog.
69 val httpAuthenticationDialog = HttpAuthenticationDialog()
71 // Add the arguments bundle to the dialog.
72 httpAuthenticationDialog.arguments = argumentsBundle
74 // Return the new dialog.
75 return httpAuthenticationDialog
79 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
80 // Get a handle for the arguments.
81 val arguments = requireArguments()
83 // Get the variables from the bundle.
84 val httpAuthHost = arguments.getString(HOST)
85 val httpAuthRealm = arguments.getString(REALM)
86 val webViewFragmentId = arguments.getLong(WEBVIEW_FRAGMENT_ID)
88 // Try to populate the alert dialog.
89 try { // Getting the WebView tab fragment will fail if Privacy Browser has been restarted.
90 // Get the current position of this WebView fragment.
91 val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
93 // Get the WebView tab fragment.
94 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
96 // Get the fragment view.
97 val fragmentView = webViewTabFragment.requireView()
99 // Get a handle for the current WebView.
100 val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
102 // Get a handle for the HTTP authentication handler.
103 val httpAuthHandler = nestedScrollWebView.httpAuthHandler
105 // Use an alert dialog builder to create the alert dialog.
106 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
109 dialogBuilder.setIcon(R.drawable.lock)
112 dialogBuilder.setTitle(R.string.http_authentication)
115 dialogBuilder.setView(R.layout.http_authentication_dialog)
117 // Set the close button listener.
118 dialogBuilder.setNegativeButton(R.string.close) { _: DialogInterface?, _: Int ->
119 if (httpAuthHandler != null) {
120 // Cancel the HTTP authentication request.
121 httpAuthHandler.cancel()
123 // Reset the HTTP authentication handler.
124 nestedScrollWebView.resetHttpAuthHandler()
128 // Set the proceed button listener.
129 dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
130 // Send the login information
131 login(httpAuthHandler, nestedScrollWebView)
134 // Create an alert dialog from the alert dialog builder.
135 val alertDialog = dialogBuilder.create()
137 // Get the alert dialog window.
138 val dialogWindow = alertDialog.window!!
140 // Get a handle for the shared preferences.
141 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
143 // Get the screenshot preference.
144 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
146 // Disable screenshots if not allowed.
147 if (!allowScreenshots) {
148 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
151 // Display the keyboard.
152 dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
154 // The alert dialog needs to be shown before the contents can be modified.
157 // Get handles for the views.
158 val realmTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_realm)!!
159 val hostTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_host)!!
160 usernameEditText = alertDialog.findViewById(R.id.http_authentication_username)!!
161 passwordEditText = alertDialog.findViewById(R.id.http_authentication_password)!!
163 // Set the realm text.
164 realmTextView.text = httpAuthRealm
166 // Initialize the host label and the spannable string builder.
167 val hostLabel = getString(R.string.host) + " "
168 val hostStringBuilder = SpannableStringBuilder(hostLabel + httpAuthHost)
170 // Set the blue color span.
171 val blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.blue_text))
173 // Setup the span to display the host name in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
174 hostStringBuilder.setSpan(blueColorSpan, hostLabel.length, hostStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
176 // Set the host text.
177 hostTextView.text = hostStringBuilder
179 // Allow the enter key on the keyboard to send the login information from the username edit text.
180 usernameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
181 // Check the key code and event.
182 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) { // The enter key was pressed.
183 // Send the login information.
184 login(httpAuthHandler, nestedScrollWebView)
186 // Manually dismiss the alert dialog.
187 alertDialog.dismiss()
189 // Consume the event.
190 return@setOnKeyListener true
191 } else { // If any other key was pressed, do not consume the event.
192 return@setOnKeyListener false
196 // Allow the enter key on the keyboard to send the login information from the password edit text.
197 passwordEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
198 // Check the key code and event.
199 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) { // The enter key was pressed.
200 // Send the login information.
201 login(httpAuthHandler, nestedScrollWebView)
203 // Manually dismiss the alert dialog.
204 alertDialog.dismiss()
206 // Consume the event.
207 return@setOnKeyListener true
208 } else { // If any other key was pressed, do not consume the event.
209 return@setOnKeyListener false
213 // Return the alert dialog.
215 } catch (exception: Exception) { // Privacy Browser was restarted and the HTTP auth handler no longer exists.
216 // Use an alert dialog builder to create an empty alert dialog.
217 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
219 // Create an empty alert dialog from the alert dialog builder.
220 val alertDialog = dialogBuilder.create()
222 // Set the flag to dismiss the dialog as soon as it is resumed.
225 // Return the alert dialog.
230 override fun onResume() {
231 // Run the default commands.
234 // Dismiss the alert dialog if the activity was restarted and the HTTP auth handler no longer exists.
240 private fun login(httpAuthHandler: HttpAuthHandler?, nestedScrollWebView: NestedScrollWebView) {
241 if (httpAuthHandler != null) {
242 // Send the login information.
243 httpAuthHandler.proceed(usernameEditText.text.toString(), passwordEditText.text.toString())
245 // Reset the HTTP authentication handler.
246 nestedScrollWebView.resetHttpAuthHandler()