Combine drawable files. https://redmine.stoutner.com/issues/794
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / HttpAuthenticationDialog.kt
1 /*
2  * Copyright © 2017-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android 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 Android 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 Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
22 import android.app.Dialog
23 import android.content.DialogInterface
24 import android.os.Bundle
25 import android.text.SpannableStringBuilder
26 import android.text.Spanned
27 import android.text.style.ForegroundColorSpan
28 import android.view.KeyEvent
29 import android.view.View
30 import android.view.WindowManager
31 import android.webkit.HttpAuthHandler
32 import android.widget.EditText
33 import android.widget.TextView
34
35 import androidx.appcompat.app.AlertDialog
36 import androidx.fragment.app.DialogFragment
37 import androidx.preference.PreferenceManager
38
39 import com.stoutner.privacybrowser.R
40 import com.stoutner.privacybrowser.activities.MainWebViewActivity
41 import com.stoutner.privacybrowser.views.NestedScrollWebView
42
43 // Define the class constants.
44 private const val HOST = "host"
45 private const val REALM = "realm"
46 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
47
48 class HttpAuthenticationDialog : DialogFragment() {
49     // Define the class variables.
50     private var dismissDialog: Boolean = false
51
52     // Declare the class views.
53     private lateinit var usernameEditText: EditText
54     private lateinit var passwordEditText: EditText
55
56     companion object {
57         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
58         @JvmStatic
59         fun displayDialog(host: String, realm: String, webViewFragmentId: Long): HttpAuthenticationDialog {
60             // Create an arguments bundle.
61             val argumentsBundle = Bundle()
62
63             // Store the variables in the bundle.
64             argumentsBundle.putString(HOST, host)
65             argumentsBundle.putString(REALM, realm)
66             argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
67
68             // Create a new instance of the HTTP authentication dialog.
69             val httpAuthenticationDialog = HttpAuthenticationDialog()
70
71             // Add the arguments bundle to the dialog.
72             httpAuthenticationDialog.arguments = argumentsBundle
73
74             // Return the new dialog.
75             return httpAuthenticationDialog
76         }
77     }
78
79     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
80         // Get a handle for the arguments.
81         val arguments = requireArguments()
82
83         // Get the variables from the bundle.
84         val httpAuthHost = arguments.getString(HOST)
85         val httpAuthRealm = arguments.getString(REALM)
86         val webViewFragmentId = arguments.getLong(WEBVIEW_FRAGMENT_ID)
87
88         // Try to populate the alert dialog.
89         try {  // Getting the WebView tab fragment will fail if Privacy Browser has been restarted.
90             // Get the current position of this WebView fragment.
91             val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
92
93             // Get the WebView tab fragment.
94             val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
95
96             // Get the fragment view.
97             val fragmentView = webViewTabFragment.requireView()
98
99             // Get a handle for the current WebView.
100             val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
101
102             // Get a handle for the HTTP authentication handler.
103             val httpAuthHandler = nestedScrollWebView.httpAuthHandler
104
105             // Use an alert dialog builder to create the alert dialog.
106             val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
107
108             // Set the icon.
109             dialogBuilder.setIcon(R.drawable.lock)
110
111             // Set the title.
112             dialogBuilder.setTitle(R.string.http_authentication)
113
114             // Set the view.
115             dialogBuilder.setView(R.layout.http_authentication_dialog)
116
117             // Set the close button listener.
118             dialogBuilder.setNegativeButton(R.string.close) { _: DialogInterface?, _: Int ->
119                 if (httpAuthHandler != null) {
120                     // Cancel the HTTP authentication request.
121                     httpAuthHandler.cancel()
122
123                     // Reset the HTTP authentication handler.
124                     nestedScrollWebView.resetHttpAuthHandler()
125                 }
126             }
127
128             // Set the proceed button listener.
129             dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
130                 // Send the login information
131                 login(httpAuthHandler, nestedScrollWebView)
132             }
133
134             // Create an alert dialog from the alert dialog builder.
135             val alertDialog = dialogBuilder.create()
136
137             // Get the alert dialog window.
138             val dialogWindow = alertDialog.window!!
139
140             // Get a handle for the shared preferences.
141             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
142
143             // Get the screenshot preference.
144             val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
145
146             // Disable screenshots if not allowed.
147             if (!allowScreenshots) {
148                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
149             }
150
151             // Display the keyboard.
152             dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
153
154             // The alert dialog needs to be shown before the contents can be modified.
155             alertDialog.show()
156
157             // Get handles for the views.
158             val realmTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_realm)!!
159             val hostTextView = alertDialog.findViewById<TextView>(R.id.http_authentication_host)!!
160             usernameEditText = alertDialog.findViewById(R.id.http_authentication_username)!!
161             passwordEditText = alertDialog.findViewById(R.id.http_authentication_password)!!
162
163             // Set the realm text.
164             realmTextView.text = httpAuthRealm
165
166             // Initialize the host label and the spannable string builder.
167             val hostLabel = getString(R.string.host) + "  "
168             val hostStringBuilder = SpannableStringBuilder(hostLabel + httpAuthHost)
169
170             // Set the blue color span.
171             val blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.blue_text))
172
173             // Setup the span to display the host name in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
174             hostStringBuilder.setSpan(blueColorSpan, hostLabel.length, hostStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
175
176             // Set the host text.
177             hostTextView.text = hostStringBuilder
178
179             // Allow the enter key on the keyboard to send the login information from the username edit text.
180             usernameEditText.setOnKeyListener { _: View?, keyCode: Int, event: KeyEvent ->
181                 // Check the key code and event.
182                 if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) {  // The enter key was pressed.
183                     // Send the login information.
184                     login(httpAuthHandler, nestedScrollWebView)
185
186                     // Manually dismiss the alert dialog.
187                     alertDialog.dismiss()
188
189                     // Consume the event.
190                     return@setOnKeyListener true
191                 } else {  // If any other key was pressed, do not consume the event.
192                     return@setOnKeyListener false
193                 }
194             }
195
196             // Allow the enter key on the keyboard to send the login information from the password edit text.
197             passwordEditText.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, nestedScrollWebView)
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             // Return the alert dialog.
214             return alertDialog
215         } catch (exception: Exception) {  // Privacy Browser was restarted and the HTTP auth handler no longer exists.
216             // Use an alert dialog builder to create an empty alert dialog.
217             val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
218
219             // Create an empty alert dialog from the alert dialog builder.
220             val alertDialog = dialogBuilder.create()
221
222             // Set the flag to dismiss the dialog as soon as it is resumed.
223             dismissDialog = true
224
225             // Return the alert dialog.
226             return alertDialog
227         }
228     }
229
230     override fun onResume() {
231         // Run the default commands.
232         super.onResume()
233
234         // Dismiss the alert dialog if the activity was restarted and the HTTP auth handler no longer exists.
235         if (dismissDialog) {
236             dialog!!.dismiss()
237         }
238     }
239
240     private fun login(httpAuthHandler: HttpAuthHandler?, nestedScrollWebView: NestedScrollWebView) {
241         if (httpAuthHandler != null) {
242             // Send the login information.
243             httpAuthHandler.proceed(usernameEditText.text.toString(), passwordEditText.text.toString())
244
245             // Reset the HTTP authentication handler.
246             nestedScrollWebView.resetHttpAuthHandler()
247         }
248     }
249 }