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