Convert the views and data classes to Kotlin. https://redmine.stoutner.com/issues/744
[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(requireContext(), 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                 if (httpAuthHandler != null) {
124                     // Cancel the HTTP authentication request.
125                     httpAuthHandler.cancel()
126
127                     // Reset the HTTP authentication handler.
128                     nestedScrollWebView.resetHttpAuthHandler()
129                 }
130             }
131
132             // Set the proceed button listener.
133             dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
134                 // Send the login information
135                 login(httpAuthHandler, nestedScrollWebView)
136             }
137
138             // Create an alert dialog from the alert dialog builder.
139             val alertDialog = dialogBuilder.create()
140
141             // Get the alert dialog window.
142             val dialogWindow = alertDialog.window!!
143
144             // Get a handle for the shared preferences.
145             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
146
147             // Get the screenshot preference.
148             val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
149
150             // Disable screenshots if not allowed.
151             if (!allowScreenshots) {
152                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
153             }
154
155             // Display the keyboard.
156             dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
157
158             // The alert dialog needs to be shown before the contents can be modified.
159             alertDialog.show()
160
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)!!
166
167             // Set the realm text.
168             realmTextView.text = httpAuthRealm
169
170             // Initialize the host label and the spannable string builder.
171             val hostLabel = getString(R.string.host) + "  "
172             val hostStringBuilder = SpannableStringBuilder(hostLabel + httpAuthHost)
173
174             // Get the current theme status.
175             val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
176
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))
181             } else {
182                 @Suppress("DEPRECATION")
183                 ForegroundColorSpan(resources.getColor(R.color.violet_700))
184             }
185
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)
188
189             // Set the host text.
190             hostTextView.text = hostStringBuilder
191
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)
198
199                     // Manually dismiss the alert dialog.
200                     alertDialog.dismiss()
201
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
206                 }
207             }
208
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)
215
216                     // Manually dismiss the alert dialog.
217                     alertDialog.dismiss()
218
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
223                 }
224             }
225
226             // Return the alert dialog.
227             return alertDialog
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)
231
232             // Create an empty alert dialog from the alert dialog builder.
233             val alertDialog = dialogBuilder.create()
234
235             // Set the flag to dismiss the dialog as soon as it is resumed.
236             dismissDialog = true
237
238             // Return the alert dialog.
239             return alertDialog
240         }
241     }
242
243     override fun onResume() {
244         // Run the default commands.
245         super.onResume()
246
247         // Dismiss the alert dialog if the activity was restarted and the HTTP auth handler no longer exists.
248         if (dismissDialog) {
249             dialog!!.dismiss()
250         }
251     }
252
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())
257
258             // Reset the HTTP authentication handler.
259             nestedScrollWebView.resetHttpAuthHandler()
260         }
261     }
262 }