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