Convert a number of files to Kotlin. https://redmine.stoutner.com/issues/641
[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 // Declare 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     // Define 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             // Get the activity's layout inflater.
119             val layoutInflater = requireActivity().layoutInflater
120
121             // Set the layout.  The parent view is `null` because it will be assigned by the alert dialog.
122             dialogBuilder.setView(layoutInflater.inflate(R.layout.http_authentication_dialog, null))
123
124             // Set the close button listener.
125             dialogBuilder.setNegativeButton(R.string.close) { _: DialogInterface?, _: Int ->
126                 // Cancel the HTTP authentication request.
127                 httpAuthHandler.cancel()
128
129                 // Reset the HTTP authentication handler.
130                 nestedScrollWebView.resetHttpAuthHandler()
131             }// Set the proceed button listener.
132             dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
133                 // Send the login information
134                 login(httpAuthHandler)
135
136                 // Reset the HTTP authentication handler.
137                 nestedScrollWebView.resetHttpAuthHandler()
138             }
139
140             // Create an alert dialog from the alert dialog builder.
141             val alertDialog = dialogBuilder.create()
142
143             // Get the alert dialog window.
144             val dialogWindow = alertDialog.window!!
145
146             // Get a handle for the shared preferences.
147             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
148
149             // Get the screenshot preference.
150             val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
151
152             // Disable screenshots if not allowed.
153             if (!allowScreenshots) {
154                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
155             }
156
157             // Display the keyboard.
158             dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
159
160             // The alert dialog needs to be shown before the contents can be modified.
161             alertDialog.show()
162
163             // Get handles for the views.
164             val realmTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_realm)!!
165             val hostTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_host)!!
166             usernameEditText = alertDialog.findViewById(R.id.http_authentication_username)!!
167             passwordEditText = alertDialog.findViewById(R.id.http_authentication_password)!!
168
169             // Set the realm text.
170             realmTextView.text = httpAuthRealm
171
172             // Initialize the host label and the spannable string builder.
173             val hostLabel = getString(R.string.host) + "  "
174             val hostStringBuilder = SpannableStringBuilder(hostLabel + httpAuthHost)
175
176             // Get the current theme status.
177             val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
178
179             // Create a blue foreground color span.
180             val blueColorSpan: ForegroundColorSpan
181
182             // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until API >= 23.
183             blueColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
184                 @Suppress("DEPRECATION")
185                 ForegroundColorSpan(resources.getColor(R.color.blue_700))
186             } else {
187                 @Suppress("DEPRECATION")
188                 ForegroundColorSpan(resources.getColor(R.color.violet_700))
189             }
190
191             // Setup the span to display the host name in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
192             hostStringBuilder.setSpan(blueColorSpan, hostLabel.length, hostStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
193
194             // Set the host text.
195             hostTextView.text = hostStringBuilder
196
197             // Allow the enter key on the keyboard to send the login information from the username edit text.
198             usernameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
199                 // Check the key code and event.
200                 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) {  // The enter key was pressed.
201                     // Send the login information.
202                     login(httpAuthHandler)
203
204                     // Manually dismiss the alert dialog.
205                     alertDialog.dismiss()
206
207                     // Consume the event.
208                     return@setOnKeyListener true
209                 } else {  // If any other key was pressed, do not consume the event.
210                     return@setOnKeyListener false
211                 }
212             }
213
214             // Allow the enter key on the keyboard to send the login information from the password edit text.
215             passwordEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
216                 // Check the key code and event.
217                 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) {  // The enter key was pressed.
218                     // Send the login information.
219                     login(httpAuthHandler)
220
221                     // Manually dismiss the alert dialog.
222                     alertDialog.dismiss()
223
224                     // Consume the event.
225                     return@setOnKeyListener true
226                 } else {  // If any other key was pressed, do not consume the event.
227                     return@setOnKeyListener false
228                 }
229             }
230
231             // Return the alert dialog.
232             return alertDialog
233         } catch (exception: Exception) {  // Privacy Browser was restarted and the HTTP auth handler no longer exists.
234             // Use an alert dialog builder to create an empty alert dialog.
235             val dialogBuilder = AlertDialog.Builder(requireActivity(), R.style.PrivacyBrowserAlertDialog)
236
237             // Create an empty alert dialog from the alert dialog builder.
238             val alertDialog = dialogBuilder.create()
239
240             // Set the flag to dismiss the dialog as soon as it is resumed.
241             dismissDialog = true
242
243             // Return the alert dialog.
244             return alertDialog
245         }
246     }
247
248     override fun onResume() {
249         // Run the default command.
250         super.onResume()
251
252         // Dismiss the alert dialog if the activity was restarted and the HTTP auth handler no longer exists.
253         if (dismissDialog) {
254             dialog!!.dismiss()
255         }
256     }
257
258     private fun login(httpAuthHandler: HttpAuthHandler) {
259         // Send the login information.
260         httpAuthHandler.proceed(usernameEditText.text.toString(), passwordEditText.text.toString())
261     }
262 }