Fix the app bar and options menu icon colors. https://redmine.stoutner.com/issues/618
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / HttpAuthenticationDialog.kt
1 /*
2  * Copyright © 2017-2020 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 package com.stoutner.privacybrowser.dialogs
20
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
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 // 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"
48
49 class HttpAuthenticationDialog: DialogFragment() {
50     // Define the class variables.
51     private var dismissDialog: Boolean = false
52
53     // Define 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.  Also, the function can then be moved out of a companion object and just become a package-level function.
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     // `@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()
85
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)
90
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)
95
96             // Get the WebView tab fragment.
97             val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
98
99             // Get the fragment view.
100             val fragmentView = webViewTabFragment.requireView()
101
102             // Get a handle for the current WebView.
103             val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
104
105             // Get a handle for the HTTP authentication handler.
106             val httpAuthHandler = nestedScrollWebView.httpAuthHandler
107
108             // Use an alert dialog builder to create the alert dialog.
109             val dialogBuilder = AlertDialog.Builder(requireActivity(), R.style.PrivacyBrowserAlertDialog)
110
111             // Set the icon according to the theme.
112             dialogBuilder.setIconAttribute(R.attr.lockBlueIcon)
113
114             // Set the title.
115             dialogBuilder.setTitle(R.string.http_authentication)
116
117             // Get the activity's layout inflater.
118             val layoutInflater = requireActivity().layoutInflater
119
120             // Set the layout.  The parent view is `null` because it will be assigned by the alert dialog.
121             dialogBuilder.setView(layoutInflater.inflate(R.layout.http_authentication_dialog, null))
122
123             // Set the close button listener.
124             dialogBuilder.setNegativeButton(R.string.close) { _: DialogInterface?, _: Int ->
125                 // Cancel the HTTP authentication request.
126                 httpAuthHandler.cancel()
127
128                 // Reset the HTTP authentication handler.
129                 nestedScrollWebView.resetHttpAuthHandler()
130             }// Set the proceed button listener.
131             dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
132                 // Send the login information
133                 login(httpAuthHandler)
134
135                 // Reset the HTTP authentication handler.
136                 nestedScrollWebView.resetHttpAuthHandler()
137             }
138
139             // Create an alert dialog from the alert dialog builder.
140             val alertDialog = dialogBuilder.create()
141
142             // Get the alert dialog window.
143             val dialogWindow = alertDialog.window!!
144
145             // Get a handle for the shared preferences.
146             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
147
148             // Get the screenshot preference.
149             val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
150
151             // Disable screenshots if not allowed.
152             if (!allowScreenshots) {
153                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
154             }
155
156             // Display the keyboard.
157             dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
158
159             // The alert dialog needs to be shown before the contents can be modified.
160             alertDialog.show()
161
162             // Get handles for the views.
163             val realmTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_realm)!!
164             val hostTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_host)!!
165             usernameEditText = alertDialog.findViewById(R.id.http_authentication_username)!!
166             passwordEditText = alertDialog.findViewById(R.id.http_authentication_password)!!
167
168             // Set the realm text.
169             realmTextView.text = httpAuthRealm
170
171             // Initialize the host label and the spannable string builder.
172             val hostLabel = getString(R.string.host) + "  "
173             val hostStringBuilder = SpannableStringBuilder(hostLabel + httpAuthHost)
174
175             // Get the current theme status.
176             val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
177
178             // Create a blue foreground color span.
179             val blueColorSpan: ForegroundColorSpan
180
181             // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until API >= 23.
182             blueColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
183                 @Suppress("DEPRECATION")
184                 ForegroundColorSpan(resources.getColor(R.color.blue_700))
185             } else {
186                 @Suppress("DEPRECATION")
187                 ForegroundColorSpan(resources.getColor(R.color.violet_500))
188             }
189
190             // Setup the span to display the host name in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
191             hostStringBuilder.setSpan(blueColorSpan, hostLabel.length, hostStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
192
193             // Set the host text.
194             hostTextView.text = hostStringBuilder
195
196             // Allow the enter key on the keyboard to send the login information from the username edit text.
197             usernameEditText.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)
202
203                     // Manually dismiss the alert dialog.
204                     alertDialog.dismiss()
205
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
210                 }
211             }
212
213             // Allow the enter key on the keyboard to send the login information from the password edit text.
214             passwordEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
215                 // Check the key code and event.
216                 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) {  // The enter key was pressed.
217                     // Send the login information.
218                     login(httpAuthHandler)
219
220                     // Manually dismiss the alert dialog.
221                     alertDialog.dismiss()
222
223                     // Consume the event.
224                     return@setOnKeyListener true
225                 } else {  // If any other key was pressed, do not consume the event.
226                     return@setOnKeyListener false
227                 }
228             }
229
230             // Return the alert dialog.
231             return alertDialog
232         } catch (exception: Exception) {  // Privacy Browser was restarted and the HTTP auth handler no longer exists.
233             // Use an alert dialog builder to create an empty alert dialog.
234             val dialogBuilder = AlertDialog.Builder(requireActivity(), R.style.PrivacyBrowserAlertDialog)
235
236             // Create an empty alert dialog from the alert dialog builder.
237             val alertDialog = dialogBuilder.create()
238
239             // Set the flag to dismiss the dialog as soon as it is resumed.
240             dismissDialog = true
241
242             // Return the alert dialog.
243             return alertDialog
244         }
245     }
246
247     override fun onResume() {
248         // Run the default command.
249         super.onResume()
250
251         // Dismiss the alert dialog if the activity was restarted and the HTTP auth handler no longer exists.
252         if (dismissDialog) {
253             dialog!!.dismiss()
254         }
255     }
256
257     private fun login(httpAuthHandler: HttpAuthHandler) {
258         // Send the login information.
259         httpAuthHandler.proceed(usernameEditText.text.toString(), passwordEditText.text.toString())
260     }
261 }