Fix a crash when opening a drawer while restarting. https://redmine.stoutner.com...
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / HttpAuthenticationDialog.kt
1 /*
2  * Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
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
35
36 import androidx.appcompat.app.AlertDialog
37 import androidx.fragment.app.DialogFragment
38 import androidx.preference.PreferenceManager
39
40 import com.stoutner.privacybrowser.R
41 import com.stoutner.privacybrowser.activities.MainWebViewActivity
42 import com.stoutner.privacybrowser.views.NestedScrollWebView
43
44 // Define 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"
48
49 class HttpAuthenticationDialog : DialogFragment() {
50     // Define the class variables.
51     private var dismissDialog: Boolean = false
52
53     // Declare the class views.
54     private lateinit var usernameEditText: EditText
55     private lateinit var passwordEditText: EditText
56
57     companion object {
58         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
59         @JvmStatic
60         fun displayDialog(host: String, realm: String, webViewFragmentId: Long): HttpAuthenticationDialog {
61             // Create an arguments bundle.
62             val argumentsBundle = Bundle()
63
64             // Store the variables in the bundle.
65             argumentsBundle.putString(HOST, host)
66             argumentsBundle.putString(REALM, realm)
67             argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
68
69             // Create a new instance of the HTTP authentication dialog.
70             val httpAuthenticationDialog = HttpAuthenticationDialog()
71
72             // Add the arguments bundle to the dialog.
73             httpAuthenticationDialog.arguments = argumentsBundle
74
75             // Return the new dialog.
76             return httpAuthenticationDialog
77         }
78     }
79
80     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
81         // Get a handle for the arguments.
82         val arguments = requireArguments()
83
84         // Get the variables from the bundle.
85         val httpAuthHost = arguments.getString(HOST)
86         val httpAuthRealm = arguments.getString(REALM)
87         val webViewFragmentId = arguments.getLong(WEBVIEW_FRAGMENT_ID)
88
89         // Try to populate the alert dialog.
90         try {  // Getting the WebView tab fragment will fail if Privacy Browser has been restarted.
91             // Get the current position of this WebView fragment.
92             val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
93
94             // Get the WebView tab fragment.
95             val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
96
97             // Get the fragment view.
98             val fragmentView = webViewTabFragment.requireView()
99
100             // Get a handle for the current WebView.
101             val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
102
103             // Get a handle for the HTTP authentication handler.
104             val httpAuthHandler = nestedScrollWebView.httpAuthHandler
105
106             // Use an alert dialog builder to create the alert dialog.
107             val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
108
109             // Set the icon according to the theme.
110             dialogBuilder.setIconAttribute(R.attr.lockBlueIcon)
111
112             // Set the title.
113             dialogBuilder.setTitle(R.string.http_authentication)
114
115             // Set the view.
116             dialogBuilder.setView(R.layout.http_authentication_dialog)
117
118             // Set the close button listener.
119             dialogBuilder.setNegativeButton(R.string.close) { _: DialogInterface?, _: Int ->
120                 if (httpAuthHandler != null) {
121                     // Cancel the HTTP authentication request.
122                     httpAuthHandler.cancel()
123
124                     // Reset the HTTP authentication handler.
125                     nestedScrollWebView.resetHttpAuthHandler()
126                 }
127             }
128
129             // Set the proceed button listener.
130             dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
131                 // Send the login information
132                 login(httpAuthHandler, nestedScrollWebView)
133             }
134
135             // Create an alert dialog from the alert dialog builder.
136             val alertDialog = dialogBuilder.create()
137
138             // Get the alert dialog window.
139             val dialogWindow = alertDialog.window!!
140
141             // Get a handle for the shared preferences.
142             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
143
144             // Get the screenshot preference.
145             val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
146
147             // Disable screenshots if not allowed.
148             if (!allowScreenshots) {
149                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
150             }
151
152             // Display the keyboard.
153             dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
154
155             // The alert dialog needs to be shown before the contents can be modified.
156             alertDialog.show()
157
158             // Get handles for the views.
159             val realmTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_realm)!!
160             val hostTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_host)!!
161             usernameEditText = alertDialog.findViewById(R.id.http_authentication_username)!!
162             passwordEditText = alertDialog.findViewById(R.id.http_authentication_password)!!
163
164             // Set the realm text.
165             realmTextView.text = httpAuthRealm
166
167             // Initialize the host label and the spannable string builder.
168             val hostLabel = getString(R.string.host) + "  "
169             val hostStringBuilder = SpannableStringBuilder(hostLabel + httpAuthHost)
170
171             // Get the current theme status.
172             val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
173
174             // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until API >= 23.
175             val blueColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
176                 @Suppress("DEPRECATION")
177                 ForegroundColorSpan(resources.getColor(R.color.blue_700))
178             } else {
179                 @Suppress("DEPRECATION")
180                 ForegroundColorSpan(resources.getColor(R.color.violet_700))
181             }
182
183             // Setup the span to display the host name in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
184             hostStringBuilder.setSpan(blueColorSpan, hostLabel.length, hostStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
185
186             // Set the host text.
187             hostTextView.text = hostStringBuilder
188
189             // Allow the enter key on the keyboard to send the login information from the username edit text.
190             usernameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
191                 // Check the key code and event.
192                 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) {  // The enter key was pressed.
193                     // Send the login information.
194                     login(httpAuthHandler, nestedScrollWebView)
195
196                     // Manually dismiss the alert dialog.
197                     alertDialog.dismiss()
198
199                     // Consume the event.
200                     return@setOnKeyListener true
201                 } else {  // If any other key was pressed, do not consume the event.
202                     return@setOnKeyListener false
203                 }
204             }
205
206             // Allow the enter key on the keyboard to send the login information from the password edit text.
207             passwordEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
208                 // Check the key code and event.
209                 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) {  // The enter key was pressed.
210                     // Send the login information.
211                     login(httpAuthHandler, nestedScrollWebView)
212
213                     // Manually dismiss the alert dialog.
214                     alertDialog.dismiss()
215
216                     // Consume the event.
217                     return@setOnKeyListener true
218                 } else {  // If any other key was pressed, do not consume the event.
219                     return@setOnKeyListener false
220                 }
221             }
222
223             // Return the alert dialog.
224             return alertDialog
225         } catch (exception: Exception) {  // Privacy Browser was restarted and the HTTP auth handler no longer exists.
226             // Use an alert dialog builder to create an empty alert dialog.
227             val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
228
229             // Create an empty alert dialog from the alert dialog builder.
230             val alertDialog = dialogBuilder.create()
231
232             // Set the flag to dismiss the dialog as soon as it is resumed.
233             dismissDialog = true
234
235             // Return the alert dialog.
236             return alertDialog
237         }
238     }
239
240     override fun onResume() {
241         // Run the default commands.
242         super.onResume()
243
244         // Dismiss the alert dialog if the activity was restarted and the HTTP auth handler no longer exists.
245         if (dismissDialog) {
246             dialog!!.dismiss()
247         }
248     }
249
250     private fun login(httpAuthHandler: HttpAuthHandler?, nestedScrollWebView: NestedScrollWebView) {
251         if (httpAuthHandler != null) {
252             // Send the login information.
253             httpAuthHandler.proceed(usernameEditText.text.toString(), passwordEditText.text.toString())
254
255             // Reset the HTTP authentication handler.
256             nestedScrollWebView.resetHttpAuthHandler()
257         }
258     }
259 }