]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
Update the proxy app bar background color. https://redmine.stoutner.com/issues/998
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.kt
1 /*
2  * Copyright 2015-2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
5  *
6  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
7  *
8  * Privacy Browser Android is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * Privacy Browser Android is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 package com.stoutner.privacybrowser.activities
23
24 import android.animation.ObjectAnimator
25 import android.annotation.SuppressLint
26 import android.app.DownloadManager
27 import android.app.SearchManager
28 import android.content.ActivityNotFoundException
29 import android.content.BroadcastReceiver
30 import android.content.ClipData
31 import android.content.ClipboardManager
32 import android.content.Context
33 import android.content.Intent
34 import android.content.IntentFilter
35 import android.content.SharedPreferences
36 import android.content.pm.PackageManager
37 import android.content.res.Configuration
38 import android.database.Cursor
39 import android.graphics.Bitmap
40 import android.graphics.BitmapFactory
41 import android.graphics.Typeface
42 import android.graphics.drawable.BitmapDrawable
43 import android.net.Uri
44 import android.net.http.SslError
45 import android.os.Build
46 import android.os.Bundle
47 import android.os.Environment
48 import android.os.Handler
49 import android.os.Looper
50 import android.print.PrintManager
51 import android.provider.DocumentsContract
52 import android.provider.OpenableColumns
53 import android.text.Editable
54 import android.text.TextWatcher
55 import android.text.style.ForegroundColorSpan
56 import android.util.Patterns
57 import android.util.TypedValue
58 import android.view.ContextMenu
59 import android.view.GestureDetector
60 import android.view.KeyEvent
61 import android.view.Menu
62 import android.view.MenuItem
63 import android.view.MotionEvent
64 import android.view.View
65 import android.view.ViewGroup
66 import android.view.WindowManager
67 import android.view.inputmethod.InputMethodManager
68 import android.webkit.CookieManager
69 import android.webkit.HttpAuthHandler
70 import android.webkit.ValueCallback
71 import android.webkit.SslErrorHandler
72 import android.webkit.WebChromeClient
73 import android.webkit.WebResourceRequest
74 import android.webkit.WebResourceResponse
75 import android.webkit.WebSettings
76 import android.webkit.WebStorage
77 import android.webkit.WebView
78 import android.webkit.WebViewClient
79 import android.webkit.WebViewDatabase
80 import android.widget.AdapterView
81 import android.widget.ArrayAdapter
82 import android.widget.CheckBox
83 import android.widget.EditText
84 import android.widget.FrameLayout
85 import android.widget.ImageView
86 import android.widget.LinearLayout
87 import android.widget.ListView
88 import android.widget.ProgressBar
89 import android.widget.RadioButton
90 import android.widget.RelativeLayout
91 import android.widget.TextView
92
93 import androidx.activity.OnBackPressedCallback
94 import androidx.activity.result.contract.ActivityResultContracts
95 import androidx.appcompat.app.ActionBar
96 import androidx.appcompat.app.ActionBarDrawerToggle
97 import androidx.appcompat.app.AppCompatActivity
98 import androidx.appcompat.app.AppCompatDelegate
99 import androidx.appcompat.content.res.AppCompatResources
100 import androidx.appcompat.widget.Toolbar
101 import androidx.coordinatorlayout.widget.CoordinatorLayout
102 import androidx.core.view.GravityCompat
103 import androidx.cursoradapter.widget.CursorAdapter
104 import androidx.drawerlayout.widget.DrawerLayout
105 import androidx.fragment.app.DialogFragment
106 import androidx.preference.PreferenceManager
107 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
108 import androidx.viewpager.widget.ViewPager
109 import androidx.webkit.WebSettingsCompat
110 import androidx.webkit.WebViewFeature
111
112 import com.google.android.material.appbar.AppBarLayout
113 import com.google.android.material.floatingactionbutton.FloatingActionButton
114 import com.google.android.material.navigation.NavigationView
115 import com.google.android.material.snackbar.Snackbar
116 import com.google.android.material.tabs.TabLayout
117
118 import com.stoutner.privacybrowser.R
119 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter
120 import com.stoutner.privacybrowser.coroutines.GetHostIpAddressesCoroutine
121 import com.stoutner.privacybrowser.coroutines.PopulateBlocklistsCoroutine
122 import com.stoutner.privacybrowser.coroutines.PrepareSaveDialogCoroutine
123 import com.stoutner.privacybrowser.coroutines.SaveUrlCoroutine
124 import com.stoutner.privacybrowser.coroutines.SaveWebpageImageCoroutine
125 import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass
126 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog
127 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog
128 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog
129 import com.stoutner.privacybrowser.dialogs.FontSizeDialog
130 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog
131 import com.stoutner.privacybrowser.dialogs.OpenDialog
132 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog
133 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog
134 import com.stoutner.privacybrowser.dialogs.SaveDialog
135 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog
136 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog
137 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog
138 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog
139 import com.stoutner.privacybrowser.fragments.WebViewTabFragment
140 import com.stoutner.privacybrowser.helpers.REQUEST_ALLOWED
141 import com.stoutner.privacybrowser.helpers.REQUEST_BLOCKED
142 import com.stoutner.privacybrowser.helpers.REQUEST_DEFAULT
143 import com.stoutner.privacybrowser.helpers.REQUEST_THIRD_PARTY
144 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper
145 import com.stoutner.privacybrowser.helpers.CheckBlocklistHelper
146 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
147 import com.stoutner.privacybrowser.helpers.ProxyHelper
148 import com.stoutner.privacybrowser.helpers.SanitizeUrlHelper
149 import com.stoutner.privacybrowser.helpers.UrlHelper
150 import com.stoutner.privacybrowser.views.BLOCKED_REQUESTS
151 import com.stoutner.privacybrowser.views.EASYLIST
152 import com.stoutner.privacybrowser.views.EASYPRIVACY
153 import com.stoutner.privacybrowser.views.FANBOYS_ANNOYANCE_LIST
154 import com.stoutner.privacybrowser.views.FANBOYS_SOCIAL_BLOCKING_LIST
155 import com.stoutner.privacybrowser.views.THIRD_PARTY_REQUESTS
156 import com.stoutner.privacybrowser.views.ULTRALIST
157 import com.stoutner.privacybrowser.views.ULTRAPRIVACY
158 import com.stoutner.privacybrowser.views.NestedScrollWebView
159
160 import kotlinx.coroutines.CoroutineScope
161 import kotlinx.coroutines.Dispatchers
162 import kotlinx.coroutines.launch
163 import kotlinx.coroutines.withContext
164
165 import java.io.ByteArrayInputStream
166 import java.io.ByteArrayOutputStream
167 import java.io.File
168 import java.io.FileInputStream
169 import java.io.FileOutputStream
170 import java.io.IOException
171 import java.io.UnsupportedEncodingException
172
173 import java.net.MalformedURLException
174 import java.net.URL
175 import java.net.URLDecoder
176 import java.net.URLEncoder
177
178 import java.text.NumberFormat
179
180 import java.util.ArrayList
181 import java.util.Date
182 import java.util.concurrent.Executors
183 import kotlin.system.exitProcess
184
185 // Define the public constants
186 const val CURRENT_URL = "current_url"
187 const val DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0
188 const val DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2
189 const val DOMAINS_CUSTOM_USER_AGENT = 12
190 const val SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1
191 const val SETTINGS_CUSTOM_USER_AGENT = 11
192 const val UNRECOGNIZED_USER_AGENT = -1
193
194 // Define the private class constants.
195 private const val BOOKMARKS_DRAWER_PINNED = "bookmarks_drawer_pinned"
196 private const val PROXY_MODE = "proxy_mode"
197 private const val SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list"
198 private const val SAVED_STATE_ARRAY_LIST = "saved_state_array_list"
199 private const val SAVED_TAB_POSITION = "saved_tab_position"
200 private const val TEMPORARY_MHT_FILE = "temporary_mht_file"
201
202 class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener,
203     NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklistsCoroutine.PopulateBlocklistsListener, SaveDialog.SaveListener,
204     UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
205
206     companion object {
207         // Define the public static variables.
208         var currentBookmarksFolder = ""
209         val executorService = Executors.newFixedThreadPool(4)!!
210         var orbotStatus = "unknown"
211         val pendingDialogsArrayList = ArrayList<PendingDialogDataClass>()
212         var proxyMode = ProxyHelper.NONE
213         var restartFromBookmarksActivity = false
214         var webViewPagerAdapter: WebViewPagerAdapter? = null
215
216         // Declare the public static variables.
217         lateinit var appBarLayout: AppBarLayout
218     }
219
220     // Declare the class variables.
221     private lateinit var appBar: ActionBar
222     private lateinit var checkBlocklistHelper: CheckBlocklistHelper
223     private lateinit var bookmarksCursorAdapter: CursorAdapter
224     private lateinit var bookmarksListView: ListView
225     private lateinit var bookmarksDrawerPinnedImageView: ImageView
226     private lateinit var bookmarksTitleTextView: TextView
227     private lateinit var coordinatorLayout: CoordinatorLayout
228     private lateinit var cookieManager: CookieManager
229     private lateinit var domainsSettingsSet: MutableSet<String>
230     private lateinit var drawerLayout: DrawerLayout
231     private lateinit var easyList: ArrayList<List<Array<String>>>
232     private lateinit var easyPrivacy: ArrayList<List<Array<String>>>
233     private lateinit var fanboysAnnoyanceList: ArrayList<List<Array<String>>>
234     private lateinit var fanboysSocialList: ArrayList<List<Array<String>>>
235     private lateinit var fileChooserCallback: ValueCallback<Array<Uri>>
236     private lateinit var finalGrayColorSpan: ForegroundColorSpan
237     private lateinit var findOnPageCountTextView: TextView
238     private lateinit var findOnPageEditText: EditText
239     private lateinit var findOnPageLinearLayout: LinearLayout
240     private lateinit var fullScreenVideoFrameLayout: FrameLayout
241     private lateinit var initialGrayColorSpan: ForegroundColorSpan
242     private lateinit var navigationBackMenuItem: MenuItem
243     private lateinit var navigationForwardMenuItem: MenuItem
244     private lateinit var navigationHistoryMenuItem: MenuItem
245     private lateinit var navigationRequestsMenuItem: MenuItem
246     private lateinit var optionsAddOrEditDomainMenuItem: MenuItem
247     private lateinit var optionsBlockAllThirdPartyRequestsMenuItem: MenuItem
248     private lateinit var optionsBlocklistsMenuItem: MenuItem
249     private lateinit var optionsClearCookiesMenuItem: MenuItem
250     private lateinit var optionsClearDataMenuItem: MenuItem
251     private lateinit var optionsClearDomStorageMenuItem: MenuItem
252     private lateinit var optionsClearFormDataMenuItem: MenuItem
253     private lateinit var optionsCookiesMenuItem: MenuItem
254     private lateinit var optionsDarkWebViewMenuItem: MenuItem
255     private lateinit var optionsDisplayImagesMenuItem: MenuItem
256     private lateinit var optionsDomStorageMenuItem: MenuItem
257     private lateinit var optionsEasyListMenuItem: MenuItem
258     private lateinit var optionsEasyPrivacyMenuItem: MenuItem
259     private lateinit var optionsFanboysAnnoyanceListMenuItem: MenuItem
260     private lateinit var optionsFanboysSocialBlockingListMenuItem: MenuItem
261     private lateinit var optionsFontSizeMenuItem: MenuItem
262     private lateinit var optionsPrivacyMenuItem: MenuItem
263     private lateinit var optionsProxyCustomMenuItem: MenuItem
264     private lateinit var optionsProxyI2pMenuItem: MenuItem
265     private lateinit var optionsProxyMenuItem: MenuItem
266     private lateinit var optionsProxyNoneMenuItem: MenuItem
267     private lateinit var optionsProxyTorMenuItem: MenuItem
268     private lateinit var optionsRefreshMenuItem: MenuItem
269     private lateinit var optionsSaveFormDataMenuItem: MenuItem
270     private lateinit var optionsSwipeToRefreshMenuItem: MenuItem
271     private lateinit var optionsUltraListMenuItem: MenuItem
272     private lateinit var optionsUltraPrivacyMenuItem: MenuItem
273     private lateinit var optionsUserAgentChromeOnAndroidMenuItem: MenuItem
274     private lateinit var optionsUserAgentChromeOnWindowsMenuItem: MenuItem
275     private lateinit var optionsUserAgentChromiumOnLinuxMenuItem: MenuItem
276     private lateinit var optionsUserAgentCustomMenuItem: MenuItem
277     private lateinit var optionsUserAgentEdgeOnWindowsMenuItem: MenuItem
278     private lateinit var optionsUserAgentFirefoxOnAndroidMenuItem: MenuItem
279     private lateinit var optionsUserAgentFirefoxOnLinuxMenuItem: MenuItem
280     private lateinit var optionsUserAgentFirefoxOnWindowsMenuItem: MenuItem
281     private lateinit var optionsUserAgentInternetExplorerOnWindowsMenuItem: MenuItem
282     private lateinit var optionsUserAgentMenuItem: MenuItem
283     private lateinit var optionsUserAgentPrivacyBrowserMenuItem: MenuItem
284     private lateinit var optionsUserAgentSafariOnIosMenuItem: MenuItem
285     private lateinit var optionsUserAgentSafariOnMacosMenuItem: MenuItem
286     private lateinit var optionsUserAgentWebViewDefaultMenuItem: MenuItem
287     private lateinit var optionsWideViewportMenuItem: MenuItem
288     private lateinit var proxyHelper: ProxyHelper
289     private lateinit var redColorSpan: ForegroundColorSpan
290     private lateinit var rootFrameLayout: FrameLayout
291     private lateinit var saveUrlString: String
292     private lateinit var searchURL: String
293     private lateinit var sharedPreferences: SharedPreferences
294     private lateinit var swipeRefreshLayout: SwipeRefreshLayout
295     private lateinit var tabLayout: TabLayout
296     private lateinit var tabsLinearLayout: LinearLayout
297     private lateinit var toolbar: Toolbar
298     private lateinit var webViewDefaultUserAgent: String
299     private lateinit var webViewPager: ViewPager
300     private lateinit var ultraList: ArrayList<List<Array<String>>>
301     private lateinit var urlEditText: EditText
302     private lateinit var urlRelativeLayout: RelativeLayout
303
304     // Define the class variables.
305     private var actionBarDrawerToggle: ActionBarDrawerToggle? = null
306     private var appBarHeight = 0
307     private var bookmarksCursor: Cursor? = null
308     private var bookmarksDatabaseHelper: BookmarksDatabaseHelper? = null
309     private var bookmarksDrawerPinned = false
310     private var bottomAppBar = false
311     private var currentWebView: NestedScrollWebView? = null
312     private var defaultProgressViewEndOffset = 0
313     private var defaultProgressViewStartOffset = 0
314     private var displayAdditionalAppBarIcons = false
315     private var displayingFullScreenVideo = false
316     private var domainsDatabaseHelper: DomainsDatabaseHelper? = null
317     private var downloadWithExternalApp = false
318     private var fullScreenBrowsingModeEnabled = false
319     private var hideAppBar = false
320     private var inFullScreenBrowsingMode = false
321     private var incognitoModeEnabled = false
322     private var loadingNewIntent = false
323     private var objectAnimator = ObjectAnimator()
324     private var optionsMenu: Menu? = null
325     private var orbotStatusBroadcastReceiver: BroadcastReceiver? = null
326     private var reapplyAppSettingsOnRestart = false
327     private var reapplyDomainSettingsOnRestart = false
328     private var sanitizeAmpRedirects = false
329     private var sanitizeTrackingQueries = false
330     private var savedProxyMode: String? = null
331     private var savedNestedScrollWebViewStateArrayList: ArrayList<Bundle>? = null
332     private var savedStateArrayList: ArrayList<Bundle>? = null
333     private var savedTabPosition = 0
334     private var scrollAppBar = false
335     private var ultraPrivacy: ArrayList<List<Array<String>>>? = null
336     private var waitingForProxy = false
337
338     // Define the save webpage image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
339     private val browseFileUploadActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
340         // Pass the file to the WebView.
341         fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(activityResult.resultCode, activityResult.data))
342     }
343
344     // Define the save URL activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
345     private val saveUrlActivityResultLauncher = registerForActivityResult<String, Uri>(ActivityResultContracts.CreateDocument("*/*")) { fileUri ->
346         // Only save the URL if the file URI is not null, which happens if the user exited the file picker by pressing back.
347         if (fileUri != null) {
348             // Instantiate the save URL coroutine.
349             val saveUrlCoroutine = SaveUrlCoroutine()
350
351             // Save the URL.
352             saveUrlCoroutine.save(this, this, saveUrlString, fileUri, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies)
353         }
354
355         // Reset the save URL string.
356         saveUrlString = ""
357     }
358
359     // Define the save webpage archive activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
360     private val saveWebpageArchiveActivityResultLauncher = registerForActivityResult<String, Uri>(ActivityResultContracts.CreateDocument("multipart/related")) { fileUri ->
361         // Only save the webpage archive if the file URI is not null, which happens if the user exited the file picker by pressing back.
362         if (fileUri != null) {
363             // Initialize the file name string from the file URI last path segment.
364             var fileNameString = fileUri.lastPathSegment
365
366             // Query the exact file name if the API >= 26.
367             if (Build.VERSION.SDK_INT >= 26) {
368                 // Get a cursor from the content resolver.
369                 val contentResolverCursor = contentResolver.query(fileUri, null, null, null)!!
370
371                 // Move to the fist row.
372                 contentResolverCursor.moveToFirst()
373
374                 // Get the file name from the cursor.
375                 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
376
377                 // Close the cursor.
378                 contentResolverCursor.close()
379             }
380
381             // Use a coroutine to save the file.
382             CoroutineScope(Dispatchers.Main).launch {
383                 try {
384                     // Create the file on the IO thread.
385                     withContext(Dispatchers.IO) {
386                         // Create a temporary MHT file.
387                         val temporaryMhtFile = File.createTempFile(TEMPORARY_MHT_FILE, ".mht", cacheDir)
388
389                         // The WebView must be accessed from the main thread.
390                         withContext(Dispatchers.Main) {
391                             currentWebView!!.saveWebArchive(temporaryMhtFile.toString(), false) { callbackValue ->
392                                 if (callbackValue != null) {  // The temporary MHT file was saved successfully.
393                                     try {
394                                         // Create a temporary MHT file input stream.
395                                         val temporaryMhtFileInputStream = FileInputStream(temporaryMhtFile)
396
397                                         // Get an output stream for the save webpage file path.
398                                         val mhtOutputStream = contentResolver.openOutputStream(fileUri)!!
399
400                                         // Create a transfer byte array.
401                                         val transferByteArray = ByteArray(1024)
402
403                                         // Create an integer to track the number of bytes read.
404                                         var bytesRead: Int
405
406                                         // Copy the temporary MHT file input stream to the MHT output stream.
407                                         while (temporaryMhtFileInputStream.read(transferByteArray).also { bytesRead = it } > 0)
408                                             mhtOutputStream.write(transferByteArray, 0, bytesRead)
409
410                                         // Close the streams.
411                                         mhtOutputStream.close()
412                                         temporaryMhtFileInputStream.close()
413
414                                         // Display a snackbar.
415                                         Snackbar.make(currentWebView!!, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
416                                     } catch (exception: Exception) {
417                                         // Display snackbar with the exception.
418                                         Snackbar.make(currentWebView!!, getString(R.string.error_saving_file, fileNameString, exception), Snackbar.LENGTH_INDEFINITE).show()
419                                     } finally {
420                                         // Delete the temporary MHT file.
421                                         temporaryMhtFile.delete()
422                                     }
423                                 } else {  // There was an unspecified error while saving the temporary MHT file.
424                                     // Display a snackbar.
425                                     Snackbar.make(currentWebView!!, getString(R.string.error_saving_file, fileNameString, getString(R.string.unknown_error)), Snackbar.LENGTH_INDEFINITE).show()
426                                 }
427                             }
428                         }
429                     }
430                 } catch (ioException: IOException) {
431                     // Display a snackbar with the IO exception.
432                     Snackbar.make(currentWebView!!, getString(R.string.error_saving_file, fileNameString, ioException), Snackbar.LENGTH_INDEFINITE).show()
433                 }
434             }
435         }
436     }
437
438     // Define the save webpage image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
439     private val saveWebpageImageActivityResultLauncher = registerForActivityResult<String, Uri>(ActivityResultContracts.CreateDocument("image/png")) { fileUri ->
440         // Only save the webpage image if the file URI is not null, which happens if the user exited the file picker by pressing back.
441         if (fileUri != null) {
442             // Instantiate the save webpage image coroutine.
443             val saveWebpageImageCoroutine = SaveWebpageImageCoroutine()
444
445             // Save the webpage image.
446             saveWebpageImageCoroutine.save(this, fileUri, currentWebView!!)
447         }
448     }
449
450     override fun onCreate(savedInstanceState: Bundle?) {
451         // Run the default commands.
452         super.onCreate(savedInstanceState)
453
454         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
455         PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
456
457         // Get a handle for the shared preferences.
458         sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
459
460         // Get the preferences.
461         val appTheme = sharedPreferences.getString(getString(R.string.app_theme_key), getString(R.string.app_theme_default_value))
462         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
463         bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
464         displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false)
465
466         // Get the theme entry values string array.
467         val appThemeEntryValuesStringArray = resources.getStringArray(R.array.app_theme_entry_values)
468
469         // Get the current theme status.
470         val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
471
472         // Set the app theme according to the preference.  A switch statement cannot be used because the theme entry values string array is not a compile time constant.
473         if (appTheme == appThemeEntryValuesStringArray[1]) {  // The light theme is selected.
474             // Apply the light theme.
475             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
476         } else if (appTheme == appThemeEntryValuesStringArray[2]) {  // The dark theme is selected.
477             // Apply the dark theme.
478             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
479         } else {  // The system default theme is selected.
480             if (Build.VERSION.SDK_INT >= 28) {  // The system default theme is supported.
481                 // Follow the system default theme.
482                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
483             } else {  // The system default theme is not supported.
484                 // Follow the battery saver mode.
485                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
486             }
487         }
488
489         // Do not continue if the app theme is different than the OS theme.  The app always initially starts in the OS theme.
490         // If the user has specified the opposite theme should be used, the app will restart in that mode after the above `setDefaultNightMode()` code processes.  However, the restart is delayed.
491         // If the blacklist coroutine starts below it will continue to run during the restart, which leads to indeterminate behavior, with the system often not knowing how many tabs exist.
492         // See https://redmine.stoutner.com/issues/952.
493         if ((appTheme == appThemeEntryValuesStringArray[0]) ||  // The system default theme is used.
494             ((appTheme == appThemeEntryValuesStringArray[1]) && (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO)) ||  // The app is running in day theme as desired.
495             ((appTheme == appThemeEntryValuesStringArray[2]) && (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES))) {  // The app is running in night theme as desired.
496
497             // Disable screenshots if not allowed.
498             if (!allowScreenshots) {
499                 window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
500             }
501
502             // Check to see if the activity has been restarted.
503             if (savedInstanceState != null) {
504                 // Store the saved instance state variables.  The deprecated `getParcelableArrayList` can be upgraded once the minimum API >= 33.
505                 bookmarksDrawerPinned = savedInstanceState.getBoolean(BOOKMARKS_DRAWER_PINNED)
506                 @Suppress("DEPRECATION")
507                 savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST)
508                 @Suppress("DEPRECATION")
509                 savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST)
510                 savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION)
511                 savedProxyMode = savedInstanceState.getString(PROXY_MODE)
512             }
513
514             // Enable the drawing of the entire webpage.  This makes it possible to save a website image.  This must be done before anything else happens with the WebView.
515             WebView.enableSlowWholeDocumentDraw()
516
517             // Set the content view according to the position of the app bar.
518             if (bottomAppBar)
519                 setContentView(R.layout.main_framelayout_bottom_appbar)
520             else
521                 setContentView(R.layout.main_framelayout_top_appbar)
522
523             // Get handles for the views.
524             rootFrameLayout = findViewById(R.id.root_framelayout)
525             drawerLayout = findViewById(R.id.drawerlayout)
526             coordinatorLayout = findViewById(R.id.coordinatorlayout)
527             appBarLayout = findViewById(R.id.appbar_layout)
528             toolbar = findViewById(R.id.toolbar)
529             findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout)
530             findOnPageEditText = findViewById(R.id.find_on_page_edittext)
531             findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview)
532             tabsLinearLayout = findViewById(R.id.tabs_linearlayout)
533             tabLayout = findViewById(R.id.tablayout)
534             swipeRefreshLayout = findViewById(R.id.swiperefreshlayout)
535             webViewPager = findViewById(R.id.webviewpager)
536             val navigationView = findViewById<NavigationView>(R.id.navigationview)
537             bookmarksListView = findViewById(R.id.bookmarks_drawer_listview)
538             bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview)
539             bookmarksDrawerPinnedImageView = findViewById(R.id.bookmarks_drawer_pinned_imageview)
540             fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout)
541
542             // Get a handle for the navigation menu.
543             val navigationMenu = navigationView.menu
544
545             // Get handles for the navigation menu items.
546             navigationBackMenuItem = navigationMenu.findItem(R.id.back)
547             navigationForwardMenuItem = navigationMenu.findItem(R.id.forward)
548             navigationHistoryMenuItem = navigationMenu.findItem(R.id.history)
549             navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests)
550
551             // Listen for touches on the navigation menu.
552             navigationView.setNavigationItemSelectedListener(this)
553
554             // Set the support action bar.
555             setSupportActionBar(toolbar)
556
557             // Get a handle for the app bar.
558             appBar = supportActionBar!!
559
560             // Set the custom app bar layout, which shows the URL text bar.
561             appBar.setCustomView(R.layout.url_app_bar)
562
563             // Display the custom app bar layout.
564             appBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
565
566             // Get handles for the views in the URL app bar.
567             urlRelativeLayout = findViewById(R.id.url_relativelayout)
568             urlEditText = findViewById(R.id.url_edittext)
569
570             // Create the hamburger icon at the start of the AppBar.
571             actionBarDrawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer)
572
573             // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
574             drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
575
576             // Initially hide the user interface so that only the blocklist loading screen is shown (if reloading).
577             drawerLayout.visibility = View.GONE
578
579             // Initialize the WebView pager adapter.
580             webViewPagerAdapter = WebViewPagerAdapter(supportFragmentManager)
581
582             // Set the pager adapter on the web view pager.
583             webViewPager.adapter = webViewPagerAdapter
584
585             // Store up to 100 tabs in memory.
586             webViewPager.offscreenPageLimit = 100
587
588             // Get a handle for the cookie manager.
589             cookieManager = CookieManager.getInstance()
590
591             // Instantiate the helpers.
592             bookmarksDatabaseHelper = BookmarksDatabaseHelper(this)
593             domainsDatabaseHelper = DomainsDatabaseHelper(this)
594             proxyHelper = ProxyHelper()
595
596             // Update the bookmarks drawer pinned image view.
597             updateBookmarksDrawerPinnedImageView()
598
599             // Initialize the app.
600             initializeApp()
601
602             // Apply the app settings from the shared preferences.
603             applyAppSettings()
604
605             // Control what the system back command does.
606             val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
607                 override fun handleOnBackPressed() {
608                     // Process the different back options.
609                     if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
610                         // Close the navigation drawer.
611                         drawerLayout.closeDrawer(GravityCompat.START)
612                     } else if (drawerLayout.isDrawerVisible(GravityCompat.END)) {  // The bookmarks drawer is open.
613                         // close the bookmarks drawer.
614                         drawerLayout.closeDrawer(GravityCompat.END)
615                     } else if (displayingFullScreenVideo) {  // A full screen video is shown.
616                         // Exit the full screen video.
617                         exitFullScreenVideo()
618                         // It shouldn't be possible for the currentWebView to be null, but crash logs indicate it sometimes happens.
619                     } else if (currentWebView != null && currentWebView!!.canGoBack()) {  // There is at least one item in the current WebView history.
620                         // Get the current web back forward list.
621                         val webBackForwardList = currentWebView!!.copyBackForwardList()
622
623                         // Get the previous entry URL.
624                         val previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.currentIndex - 1).url
625
626                         // Apply the domain settings.
627                         applyDomainSettings(currentWebView!!, previousUrl, resetTab = false, reloadWebsite = false, loadUrl = false)
628
629                         // Go back.
630                         currentWebView!!.goBack()
631                     } else {  // Close the current tab.
632                         // A view is required because the method is also called by an XML `onClick`.
633                         closeTab(null)
634                     }
635                 }
636             }
637
638             // Register the on back pressed callback.
639             onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
640
641             // Instantiate the populate blocklists coroutine.
642             val populateBlocklistsCoroutine = PopulateBlocklistsCoroutine(this)
643
644             // Populate the blocklists.
645             populateBlocklistsCoroutine.populateBlocklists(this)
646         }
647     }
648
649     public override fun onPostCreate(savedInstanceState: Bundle?) {
650         // Run the default commands.
651         super.onPostCreate(savedInstanceState)
652
653         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
654         // If the app is restarting to change the app theme the action bar drawer toggle will not yet be populated.
655         actionBarDrawerToggle?.syncState()
656     }
657
658     override fun onNewIntent(intent: Intent) {
659         // Run the default commands.
660         super.onNewIntent(intent)
661
662         // Get the information from the intent.
663         val intentAction = intent.action
664         val intentUriData = intent.data
665         val intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT)
666
667         // Determine if this is a web search.
668         val isWebSearch = (intentAction != null) && (intentAction == Intent.ACTION_WEB_SEARCH)
669
670         // Check to see if the app is being restarted from a saved state.
671         if (ultraPrivacy != null) {  // The activity is not being restarted from a saved state.
672             // Only process the URI if it contains data or it is a web search.  If the user pressed the desktop icon after the app was already running the URI will be null.
673             if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch) {
674                 // Exit the full screen video if it is displayed.
675                 if (displayingFullScreenVideo) {
676                     // Exit full screen video mode.
677                     exitFullScreenVideo()
678
679                     // Reload the current WebView.  Otherwise, it can display entirely black.
680                     currentWebView!!.reload()
681                 }
682
683                 // Get the URL.
684                 val url = if (isWebSearch) {  // The intent is a web search.
685                     // Sanitize the search input and convert it to a search.
686                     val encodedSearchString = try {
687                         URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8")
688                     } catch (exception: UnsupportedEncodingException) {
689                         ""
690                     }
691
692                     // Add the base search URL.
693                     searchURL + encodedSearchString
694                 } else {  // The intent contains a URL in either the data or an extra.
695                     intentUriData?.toString() ?: intentStringExtra
696                 }
697
698                 // Add a new tab if specified in the preferences.
699                 if (sharedPreferences.getBoolean(getString(R.string.open_intents_in_new_tab_key), true)) {  // Load the URL in a new tab.
700                     // Set the loading new intent flag.
701                     loadingNewIntent = true
702
703                     // Add a new tab.
704                     addNewTab(url!!, true)
705                 } else {  // Load the URL in the current tab.
706                     // Make it so.
707                     loadUrl(currentWebView!!, url!!)
708                 }
709
710                 // Close the navigation drawer if it is open.
711                 if (drawerLayout.isDrawerVisible(GravityCompat.START))
712                     drawerLayout.closeDrawer(GravityCompat.START)
713
714                 // Close the bookmarks drawer if it is open.
715                 if (drawerLayout.isDrawerVisible(GravityCompat.END))
716                     drawerLayout.closeDrawer(GravityCompat.END)
717             }
718         } else {  // The app has been restarted.
719             // If the new intent will open a new tab, set the saved tab position to be the size of the saved state array list.
720             // The tab position is 0 based, meaning the at the new tab will be the tab position that is restored.
721             if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch)
722                 savedTabPosition = savedStateArrayList!!.size
723
724             // Replace the intent that started the app with this one.  This will load the tab after the others have been restored.
725             setIntent(intent)
726         }
727     }
728
729     public override fun onRestart() {
730         // Run the default commands.
731         super.onRestart()
732
733         // Apply the app settings if returning from the Settings activity.
734         if (reapplyAppSettingsOnRestart) {
735             // Reset the reapply app settings on restart flag.
736             reapplyAppSettingsOnRestart = false
737
738             // Apply the app settings.
739             applyAppSettings()
740         }
741
742         // Apply the domain settings if returning from the settings or domains activity.
743         if (reapplyDomainSettingsOnRestart) {
744             // Reset the reapply domain settings on restart flag.
745             reapplyDomainSettingsOnRestart = false
746
747             // Update the domains settings set.
748             updateDomainsSettingsSet()
749
750             // Reapply the domain settings for each tab.
751             for (i in 0 until webViewPagerAdapter!!.count) {
752                 // Get the WebView tab fragment.
753                 val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
754
755                 // Get the fragment view.
756                 val fragmentView = webViewTabFragment.view
757
758                 // Only reload the WebViews if they exist.
759                 if (fragmentView != null) {
760                     // Get the nested scroll WebView from the tab fragment.
761                     val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
762
763                     // Reset the current domain name so the domain settings will be reapplied.
764                     nestedScrollWebView.currentDomainName = ""
765
766                     // Reapply the domain settings if the URL is not null, which happens for empty tabs when returning from settings.
767                     if (nestedScrollWebView.url != null)
768                         applyDomainSettings(nestedScrollWebView, nestedScrollWebView.url, resetTab = false, reloadWebsite = true, loadUrl = false)
769                 }
770             }
771         }
772
773         // Update the bookmarks drawer if returning from the Bookmarks activity.
774         if (restartFromBookmarksActivity) {
775             // Reset the restart from bookmarks activity flag.
776             restartFromBookmarksActivity = false
777
778             // Close the bookmarks drawer.
779             drawerLayout.closeDrawer(GravityCompat.END)
780
781             // Reload the bookmarks drawer.
782             loadBookmarksFolder()
783         }
784
785         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
786         updatePrivacyIcons(true)
787     }
788
789     // `onStart()` runs after `onCreate()` or `onRestart()`.  This is used instead of `onResume()` so the commands aren't called every time the screen is partially hidden.
790     public override fun onStart() {
791         // Run the default commands.
792         super.onStart()
793
794         // Resume any WebViews if the pager adapter exists.  If the app is restarting to change the initial app theme it won't have been populated yet.
795         if (webViewPagerAdapter != null) {
796             for (i in 0 until webViewPagerAdapter!!.count) {
797                 // Get the WebView tab fragment.
798                 val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
799
800                 // Get the fragment view.
801                 val fragmentView = webViewTabFragment.view
802
803                 // Only resume the WebViews if they exist (they won't when the app is first created).
804                 if (fragmentView != null) {
805                     // Get the nested scroll WebView from the tab fragment.
806                     val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
807
808                     // Resume the nested scroll WebView.
809                     nestedScrollWebView.onResume()
810                 }
811             }
812         }
813
814         // Resume the nested scroll WebView JavaScript timers.  This is a global command that resumes JavaScript timers on all WebViews.
815         if (currentWebView != null)
816             currentWebView!!.resumeTimers()
817
818         // Reapply the proxy settings if the system is using a proxy.  This redisplays the appropriate alert dialog.
819         if (proxyMode != ProxyHelper.NONE)
820             applyProxy(false)
821
822         // Reapply any system UI flags.
823         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {  // The system is displaying a website or a video in full screen mode.
824             /* Hide the system bars.
825              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
826              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
827              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
828              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
829              */
830
831             // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
832             @Suppress("DEPRECATION")
833             rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
834         }
835
836         // Show any pending dialogs.
837         for (i in pendingDialogsArrayList.indices) {
838             // Get the pending dialog from the array list.
839             val (dialogFragment, tag) = pendingDialogsArrayList[i]
840
841             // Show the pending dialog.
842             dialogFragment.show(supportFragmentManager, tag)
843         }
844
845         // Clear the pending dialogs array list.
846         pendingDialogsArrayList.clear()
847     }
848
849     public override fun onSaveInstanceState(savedInstanceState: Bundle) {
850         // Run the default commands.
851         super.onSaveInstanceState(savedInstanceState)
852
853         // Only save the instance state if the WebView pager adapter is not null, which will be the case if the app is restarting to change the initial app theme.
854         if (webViewPagerAdapter != null) {
855             // Initialize the saved state array lists.
856             savedStateArrayList = ArrayList<Bundle>()
857             savedNestedScrollWebViewStateArrayList = ArrayList<Bundle>()
858
859             // Get the URLs from each tab.
860             for (i in 0 until webViewPagerAdapter!!.count) {
861                 // Get the WebView tab fragment.
862                 val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
863
864                 // Get the fragment view.
865                 val fragmentView = webViewTabFragment.view
866
867                 // Save the fragment state if it is not null.
868                 if (fragmentView != null) {
869                     // Get the nested scroll WebView from the tab fragment.
870                     val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
871
872                     // Create the saved state bundle.
873                     val savedStateBundle = Bundle()
874
875                     // Get the current states.
876                     nestedScrollWebView.saveState(savedStateBundle)
877                     val savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState()
878
879                     // Store the saved states in the array lists.
880                     savedStateArrayList!!.add(savedStateBundle)
881                     savedNestedScrollWebViewStateArrayList!!.add(savedNestedScrollWebViewStateBundle)
882                 }
883             }
884
885             // Get the current tab position.
886             val currentTabPosition = tabLayout.selectedTabPosition
887
888             // Store the saved states in the bundle.
889             savedInstanceState.putBoolean(BOOKMARKS_DRAWER_PINNED, bookmarksDrawerPinned)
890             savedInstanceState.putString(PROXY_MODE, proxyMode)
891             savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList)
892             savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList)
893             savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition)
894         }
895     }
896
897     // `onStop()` runs after `onPause()`.  It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
898     public override fun onStop() {
899         // Run the default commands.
900         super.onStop()
901
902         // Only pause the WebViews if the pager adapter is not null, which is the case if the app is restarting to change the initial app theme.
903         if (webViewPagerAdapter != null) {
904             // Pause each web view.
905             for (i in 0 until webViewPagerAdapter!!.count) {
906                 // Get the WebView tab fragment.
907                 val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
908
909                 // Get the fragment view.
910                 val fragmentView = webViewTabFragment.view
911
912                 // Only pause the WebViews if they exist (they won't when the app is first created).
913                 if (fragmentView != null) {
914                     // Get the nested scroll WebView from the tab fragment.
915                     val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
916
917                     // Pause the nested scroll WebView.
918                     nestedScrollWebView.onPause()
919                 }
920             }
921         }
922
923         // Pause the WebView JavaScript timers.  This is a global command that pauses JavaScript on all WebViews.
924         if (currentWebView != null)
925             currentWebView!!.pauseTimers()
926     }
927
928     public override fun onDestroy() {
929         // Unregister the orbot status broadcast receiver if it exists.
930         if (orbotStatusBroadcastReceiver != null) {
931             unregisterReceiver(orbotStatusBroadcastReceiver)
932         }
933
934         // Close the bookmarks cursor if it exists.
935         bookmarksCursor?.close()
936
937         // Close the databases if they exist.
938         bookmarksDatabaseHelper?.close()
939         domainsDatabaseHelper?.close()
940
941         // Run the default commands.
942         super.onDestroy()
943     }
944
945     override fun onCreateOptionsMenu(menu: Menu): Boolean {
946         // Inflate the menu.  This adds items to the app bar if it is present.
947         menuInflater.inflate(R.menu.webview_options_menu, menu)
948
949         // Get handles for the menu items.
950         optionsPrivacyMenuItem = menu.findItem(R.id.javascript)
951         optionsRefreshMenuItem = menu.findItem(R.id.refresh)
952         val optionsBookmarksMenuItem = menu.findItem(R.id.bookmarks)
953         optionsCookiesMenuItem = menu.findItem(R.id.cookies)
954         optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage)
955         optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data) // Form data can be removed once the minimum API >= 26.
956         optionsClearDataMenuItem = menu.findItem(R.id.clear_data)
957         optionsClearCookiesMenuItem = menu.findItem(R.id.clear_cookies)
958         optionsClearDomStorageMenuItem = menu.findItem(R.id.clear_dom_storage)
959         optionsClearFormDataMenuItem = menu.findItem(R.id.clear_form_data) // Form data can be removed once the minimum API >= 26.
960         optionsBlocklistsMenuItem = menu.findItem(R.id.blocklists)
961         optionsEasyListMenuItem = menu.findItem(R.id.easylist)
962         optionsEasyPrivacyMenuItem = menu.findItem(R.id.easyprivacy)
963         optionsFanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list)
964         optionsFanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list)
965         optionsUltraListMenuItem = menu.findItem(R.id.ultralist)
966         optionsUltraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy)
967         optionsBlockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests)
968         optionsProxyMenuItem = menu.findItem(R.id.proxy)
969         optionsProxyNoneMenuItem = menu.findItem(R.id.proxy_none)
970         optionsProxyTorMenuItem = menu.findItem(R.id.proxy_tor)
971         optionsProxyI2pMenuItem = menu.findItem(R.id.proxy_i2p)
972         optionsProxyCustomMenuItem = menu.findItem(R.id.proxy_custom)
973         optionsUserAgentMenuItem = menu.findItem(R.id.user_agent)
974         optionsUserAgentPrivacyBrowserMenuItem = menu.findItem(R.id.user_agent_privacy_browser)
975         optionsUserAgentWebViewDefaultMenuItem = menu.findItem(R.id.user_agent_webview_default)
976         optionsUserAgentFirefoxOnAndroidMenuItem = menu.findItem(R.id.user_agent_firefox_on_android)
977         optionsUserAgentChromeOnAndroidMenuItem = menu.findItem(R.id.user_agent_chrome_on_android)
978         optionsUserAgentSafariOnIosMenuItem = menu.findItem(R.id.user_agent_safari_on_ios)
979         optionsUserAgentFirefoxOnLinuxMenuItem = menu.findItem(R.id.user_agent_firefox_on_linux)
980         optionsUserAgentChromiumOnLinuxMenuItem = menu.findItem(R.id.user_agent_chromium_on_linux)
981         optionsUserAgentFirefoxOnWindowsMenuItem = menu.findItem(R.id.user_agent_firefox_on_windows)
982         optionsUserAgentChromeOnWindowsMenuItem = menu.findItem(R.id.user_agent_chrome_on_windows)
983         optionsUserAgentEdgeOnWindowsMenuItem = menu.findItem(R.id.user_agent_edge_on_windows)
984         optionsUserAgentInternetExplorerOnWindowsMenuItem = menu.findItem(R.id.user_agent_internet_explorer_on_windows)
985         optionsUserAgentSafariOnMacosMenuItem = menu.findItem(R.id.user_agent_safari_on_macos)
986         optionsUserAgentCustomMenuItem = menu.findItem(R.id.user_agent_custom)
987         optionsSwipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh)
988         optionsWideViewportMenuItem = menu.findItem(R.id.wide_viewport)
989         optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images)
990         optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview)
991         optionsFontSizeMenuItem = menu.findItem(R.id.font_size)
992         optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain)
993
994         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
995         updatePrivacyIcons(false)
996
997         // Only display the form data menu items if the API < 26.
998         optionsSaveFormDataMenuItem.isVisible = Build.VERSION.SDK_INT < 26
999         optionsClearFormDataMenuItem.isVisible = Build.VERSION.SDK_INT < 26
1000
1001         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
1002         optionsClearFormDataMenuItem.isEnabled = Build.VERSION.SDK_INT < 26
1003
1004         // Only display the dark WebView menu item if the API >= 29.
1005         optionsDarkWebViewMenuItem.isVisible = Build.VERSION.SDK_INT >= 29
1006
1007         // Set the status of the additional app bar icons.  Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
1008         if (displayAdditionalAppBarIcons) {  // Display the additional icons.
1009             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
1010             optionsBookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
1011             optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
1012         } else { //Do not display the additional icons.
1013             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
1014             optionsBookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
1015             optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
1016         }
1017
1018         // Replace `Refresh` with `Stop` if a URL is already loading.
1019         if ((currentWebView != null) && (currentWebView!!.progress != 100)) {
1020             // Set the title.
1021             optionsRefreshMenuItem.setTitle(R.string.stop)
1022
1023             // Set the icon if it is displayed in the app bar.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
1024             if (displayAdditionalAppBarIcons)
1025                 optionsRefreshMenuItem.setIcon(R.drawable.close_blue)
1026         }
1027
1028         // Store a handle for the options menu.
1029         optionsMenu = menu
1030
1031         // Done.
1032         return true
1033     }
1034
1035     override fun onPrepareOptionsMenu(menu: Menu): Boolean {
1036         // Initialize the current user agent string and the font size.
1037         var currentUserAgent = getString(R.string.user_agent_privacy_browser)
1038         var fontSize = 100
1039
1040         // Set items that require the current web view to be populated.  It will be null when the program is first opened, as `onPrepareOptionsMenu()` is called before the first WebView is initialized.
1041         if (currentWebView != null) {
1042             // Set the add or edit domain text.
1043             if (currentWebView!!.domainSettingsApplied)
1044                 optionsAddOrEditDomainMenuItem.setTitle(R.string.edit_domain_settings)
1045             else
1046                 optionsAddOrEditDomainMenuItem.setTitle(R.string.add_domain_settings)
1047
1048             // Get the current user agent from the WebView.
1049             currentUserAgent = currentWebView!!.settings.userAgentString
1050
1051             // Get the current font size from the the WebView.
1052             fontSize = currentWebView!!.settings.textZoom
1053
1054             // Set the status of the menu item checkboxes.
1055             optionsDomStorageMenuItem.isChecked = currentWebView!!.settings.domStorageEnabled
1056             @Suppress("DEPRECATION")
1057             optionsSaveFormDataMenuItem.isChecked = currentWebView!!.settings.saveFormData // Form data can be removed once the minimum API >= 26.
1058             optionsEasyListMenuItem.isChecked = currentWebView!!.easyListEnabled
1059             optionsEasyPrivacyMenuItem.isChecked = currentWebView!!.easyPrivacyEnabled
1060             optionsFanboysAnnoyanceListMenuItem.isChecked = currentWebView!!.fanboysAnnoyanceListEnabled
1061             optionsFanboysSocialBlockingListMenuItem.isChecked = currentWebView!!.fanboysSocialBlockingListEnabled
1062             optionsUltraListMenuItem.isChecked = currentWebView!!.ultraListEnabled
1063             optionsUltraPrivacyMenuItem.isChecked = currentWebView!!.ultraPrivacyEnabled
1064             optionsBlockAllThirdPartyRequestsMenuItem.isChecked = currentWebView!!.blockAllThirdPartyRequests
1065             optionsSwipeToRefreshMenuItem.isChecked = currentWebView!!.swipeToRefresh
1066             optionsWideViewportMenuItem.isChecked = currentWebView!!.settings.useWideViewPort
1067             optionsDisplayImagesMenuItem.isChecked = currentWebView!!.settings.loadsImagesAutomatically
1068
1069             // Initialize the display names for the blocklists with the number of blocked requests.
1070             optionsBlocklistsMenuItem.title = getString(R.string.blocklists) + " - " + currentWebView!!.getRequestsCount(BLOCKED_REQUESTS)
1071             optionsEasyListMenuItem.title = currentWebView!!.getRequestsCount(EASYLIST).toString() + " - " + getString(R.string.easylist)
1072             optionsEasyPrivacyMenuItem.title = currentWebView!!.getRequestsCount(EASYPRIVACY).toString() + " - " + getString(R.string.easyprivacy)
1073             optionsFanboysAnnoyanceListMenuItem.title = currentWebView!!.getRequestsCount(FANBOYS_ANNOYANCE_LIST).toString() + " - " + getString(R.string.fanboys_annoyance_list)
1074             optionsFanboysSocialBlockingListMenuItem.title = currentWebView!!.getRequestsCount(FANBOYS_SOCIAL_BLOCKING_LIST).toString() + " - " + getString(R.string.fanboys_social_blocking_list)
1075             optionsUltraListMenuItem.title = currentWebView!!.getRequestsCount(ULTRALIST).toString() + " - " + getString(R.string.ultralist)
1076             optionsUltraPrivacyMenuItem.title = currentWebView!!.getRequestsCount(ULTRAPRIVACY).toString() + " - " + getString(R.string.ultraprivacy)
1077             optionsBlockAllThirdPartyRequestsMenuItem.title = currentWebView!!.getRequestsCount(THIRD_PARTY_REQUESTS).toString() + " - " + getString(R.string.block_all_third_party_requests)
1078
1079             // Enable DOM Storage if JavaScript is enabled.
1080             optionsDomStorageMenuItem.isEnabled = currentWebView!!.settings.javaScriptEnabled
1081
1082             // Get the current theme status.
1083             val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
1084
1085             // Enable dark WebView if night mode is enabled.
1086             optionsDarkWebViewMenuItem.isEnabled = (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)
1087
1088             // Set the checkbox status for dark WebView if the device is running API >= 29 and algorithmic darkening is supported.
1089             if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING))
1090                 optionsDarkWebViewMenuItem.isChecked = WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView!!.settings)
1091         }
1092
1093         // Set the cookies menu item checked status.
1094         optionsCookiesMenuItem.isChecked = cookieManager.acceptCookie()
1095
1096         // Enable Clear Cookies if there are any.
1097         optionsClearCookiesMenuItem.isEnabled = cookieManager.hasCookies()
1098
1099         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1100         val privateDataDirectoryString = applicationInfo.dataDir
1101
1102         // Get the storage directories.
1103         val localStorageDirectory = File("$privateDataDirectoryString/app_webview/Local Storage/")
1104         val indexedDBDirectory = File("$privateDataDirectoryString/app_webview/IndexedDB")
1105
1106         // Initialize the number of files counters.
1107         var localStorageDirectoryNumberOfFiles = 0
1108         var indexedDBDirectoryNumberOfFiles = 0
1109
1110         // Get a count of the number of files in the Local Storage directory.  The list can be null, in which case a `0` is returned.
1111         if (localStorageDirectory.exists())
1112             localStorageDirectoryNumberOfFiles = (localStorageDirectory.list())?.size ?: 0
1113
1114         // Get a count of the number of files in the IndexedDB directory.  The list can be null, in which case a `0` is returned.
1115         if (indexedDBDirectory.exists())
1116             indexedDBDirectoryNumberOfFiles = (indexedDBDirectory.list())?.size ?: 0
1117
1118         // Enable Clear DOM Storage if there is any.
1119         optionsClearDomStorageMenuItem.isEnabled = localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0
1120
1121         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
1122         if (Build.VERSION.SDK_INT < 26) {
1123             // Get the WebView database.
1124             val webViewDatabase = WebViewDatabase.getInstance(this)
1125
1126             // Enable the clear form data menu item if there is anything to clear.
1127             @Suppress("DEPRECATION")
1128             optionsClearFormDataMenuItem.isEnabled = webViewDatabase.hasFormData()
1129         }
1130
1131         // Enable Clear Data if any of the submenu items are enabled.
1132         optionsClearDataMenuItem.isEnabled = (optionsClearCookiesMenuItem.isEnabled || optionsClearDomStorageMenuItem.isEnabled || optionsClearFormDataMenuItem.isEnabled)
1133
1134         // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
1135         optionsFanboysSocialBlockingListMenuItem.isEnabled = !optionsFanboysAnnoyanceListMenuItem.isChecked
1136
1137         // Set the proxy title and check the applied proxy.
1138         when (proxyMode) {
1139             ProxyHelper.NONE -> {
1140                 // Set the proxy title.
1141                 optionsProxyMenuItem.title = getString(R.string.proxy) + " - " + getString(R.string.proxy_none)
1142
1143                 // Check the proxy None radio button.
1144                 optionsProxyNoneMenuItem.isChecked = true
1145             }
1146
1147             ProxyHelper.TOR -> {
1148                 // Set the proxy title.
1149                 optionsProxyMenuItem.title = getString(R.string.proxy) + " - " + getString(R.string.proxy_tor)
1150
1151                 // Check the proxy Tor radio button.
1152                 optionsProxyTorMenuItem.isChecked = true
1153             }
1154
1155             ProxyHelper.I2P -> {
1156                 // Set the proxy title.
1157                 optionsProxyMenuItem.title = getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p)
1158
1159                 // Check the proxy I2P radio button.
1160                 optionsProxyI2pMenuItem.isChecked = true
1161             }
1162
1163             ProxyHelper.CUSTOM -> {
1164                 // Set the proxy title.
1165                 optionsProxyMenuItem.title = getString(R.string.proxy) + " - " + getString(R.string.proxy_custom)
1166
1167                 // Check the proxy Custom radio button.
1168                 optionsProxyCustomMenuItem.isChecked = true
1169             }
1170         }
1171
1172         // Select the current user agent menu item.
1173         when (currentUserAgent) {
1174             resources.getStringArray(R.array.user_agent_data)[0] -> {  // Privacy Browser.
1175                 // Update the user agent menu item title.
1176                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser)
1177
1178                 // Select the Privacy Browser radio box.
1179                 optionsUserAgentPrivacyBrowserMenuItem.isChecked = true
1180             }
1181
1182             webViewDefaultUserAgent -> {  // WebView Default.
1183                 // Update the user agent menu item title.
1184                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default)
1185
1186                 // Select the WebView Default radio box.
1187                 optionsUserAgentWebViewDefaultMenuItem.isChecked = true
1188             }
1189
1190             resources.getStringArray(R.array.user_agent_data)[2] -> {  // Firefox on Android.
1191                 // Update the user agent menu item title.
1192                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android)
1193
1194                 // Select the Firefox on Android radio box.
1195                 optionsUserAgentFirefoxOnAndroidMenuItem.isChecked = true
1196             }
1197
1198             resources.getStringArray(R.array.user_agent_data)[3] -> {  // Chrome on Android.
1199                 // Update the user agent menu item title.
1200                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android)
1201
1202                 // Select the Chrome on Android radio box.
1203                 optionsUserAgentChromeOnAndroidMenuItem.isChecked = true
1204             }
1205
1206             resources.getStringArray(R.array.user_agent_data)[4] -> {  // Safari on iOS.
1207                 // Update the user agent menu item title.
1208                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios)
1209
1210                 // Select the Safari on iOS radio box.
1211                 optionsUserAgentSafariOnIosMenuItem.isChecked = true
1212             }
1213
1214             resources.getStringArray(R.array.user_agent_data)[5] -> {  // Firefox on Linux.
1215                 // Update the user agent menu item title.
1216                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux)
1217
1218                 // Select the Firefox on Linux radio box.
1219                 optionsUserAgentFirefoxOnLinuxMenuItem.isChecked = true
1220             }
1221
1222             resources.getStringArray(R.array.user_agent_data)[6] -> {  // Chromium on Linux.
1223                 // Update the user agent menu item title.
1224                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux)
1225
1226                 // Select the Chromium on Linux radio box.
1227                 optionsUserAgentChromiumOnLinuxMenuItem.isChecked = true
1228             }
1229
1230             resources.getStringArray(R.array.user_agent_data)[7] -> {  // Firefox on Windows.
1231                 // Update the user agent menu item title.
1232                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows)
1233
1234                 // Select the Firefox on Windows radio box.
1235                 optionsUserAgentFirefoxOnWindowsMenuItem.isChecked = true
1236             }
1237
1238             resources.getStringArray(R.array.user_agent_data)[8] -> {  // Chrome on Windows.
1239                 // Update the user agent menu item title.
1240                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows)
1241
1242                 // Select the Chrome on Windows radio box.
1243                 optionsUserAgentChromeOnWindowsMenuItem.isChecked = true
1244             }
1245
1246             resources.getStringArray(R.array.user_agent_data)[9] -> {  // Edge on Windows.
1247                 // Update the user agent menu item title.
1248                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows)
1249
1250                 // Select the Edge on Windows radio box.
1251                 optionsUserAgentEdgeOnWindowsMenuItem.isChecked = true
1252             }
1253
1254             resources.getStringArray(R.array.user_agent_data)[10] -> {  // Internet Explorer on Windows.
1255                 // Update the user agent menu item title.
1256                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows)
1257
1258                 // Select the Internet on Windows radio box.
1259                 optionsUserAgentInternetExplorerOnWindowsMenuItem.isChecked = true
1260             }
1261
1262             resources.getStringArray(R.array.user_agent_data)[11] -> {  // Safari on macOS.
1263                 // Update the user agent menu item title.
1264                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos)
1265
1266                 // Select the Safari on macOS radio box.
1267                 optionsUserAgentSafariOnMacosMenuItem.isChecked = true
1268             }
1269
1270             else -> {  // Custom user agent.
1271                 // Update the user agent menu item title.
1272                 optionsUserAgentMenuItem.title = getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom)
1273
1274                 // Select the Custom radio box.
1275                 optionsUserAgentCustomMenuItem.isChecked = true
1276             }
1277         }
1278
1279         // Set the font size title.
1280         optionsFontSizeMenuItem.title = getString(R.string.font_size) + " - " + fontSize + "%"
1281
1282         // Run all the other default commands.
1283         super.onPrepareOptionsMenu(menu)
1284
1285         // Display the menu.
1286         return true
1287     }
1288
1289     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
1290         // Run the commands that correlate to the selected menu item.
1291         return when (menuItem.itemId) {
1292             R.id.javascript -> {  // JavaScript.
1293                 // Toggle the JavaScript status.
1294                 currentWebView!!.settings.javaScriptEnabled = !currentWebView!!.settings.javaScriptEnabled
1295
1296                 // Update the privacy icon.
1297                 updatePrivacyIcons(true)
1298
1299                 // Display a snackbar.
1300                 if (currentWebView!!.settings.javaScriptEnabled)  // JavaScrip is enabled.
1301                     Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show()
1302                 else if (cookieManager.acceptCookie())  // JavaScript is disabled, but cookies are enabled.
1303                     Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show()
1304                 else  // Privacy mode.
1305                     Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show()
1306
1307                 // Reload the current WebView.
1308                 currentWebView!!.reload()
1309
1310                 // Consume the event.
1311                 true
1312             }
1313
1314             R.id.refresh -> {  // Refresh.
1315                 // Run the command that correlates to the current status of the menu item.
1316                 if (menuItem.title == getString(R.string.refresh))  // The refresh button was pushed.
1317                     currentWebView!!.reload()
1318                 else  // The stop button was pushed.
1319                     currentWebView!!.stopLoading()
1320
1321                 // Consume the event.
1322                 true
1323             }
1324
1325             R.id.bookmarks -> {  // Bookmarks.
1326                 // Open the bookmarks drawer.
1327                 drawerLayout.openDrawer(GravityCompat.END)
1328
1329                 // Consume the event.
1330                 true
1331             }
1332
1333             R.id.cookies -> {  // Cookies.
1334                 // Toggle the cookie status.
1335                 cookieManager.setAcceptCookie(!cookieManager.acceptCookie())
1336
1337                 // Store the cookie status.
1338                 currentWebView!!.acceptCookies = cookieManager.acceptCookie()
1339
1340                 // Update the menu checkbox.
1341                 menuItem.isChecked = cookieManager.acceptCookie()
1342
1343                 // Update the privacy icon.
1344                 updatePrivacyIcons(true)
1345
1346                 // Display a snackbar.
1347                 if (cookieManager.acceptCookie())  // Cookies are enabled.
1348                     Snackbar.make(webViewPager, R.string.cookies_enabled, Snackbar.LENGTH_SHORT).show()
1349                 else if (currentWebView!!.settings.javaScriptEnabled)  // JavaScript is still enabled.
1350                     Snackbar.make(webViewPager, R.string.cookies_disabled, Snackbar.LENGTH_SHORT).show()
1351                 else  // Privacy mode.
1352                     Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show()
1353
1354                 // Reload the current WebView.
1355                 currentWebView!!.reload()
1356
1357                 // Consume the event.
1358                 true
1359             }
1360
1361             R.id.dom_storage -> {  // DOM storage.
1362                 // Toggle the DOM storage status.
1363                 currentWebView!!.settings.domStorageEnabled = !currentWebView!!.settings.domStorageEnabled
1364
1365                 // Update the menu checkbox.
1366                 menuItem.isChecked = currentWebView!!.settings.domStorageEnabled
1367
1368                 // Update the privacy icon.
1369                 updatePrivacyIcons(true)
1370
1371                 // Display a snackbar.
1372                 if (currentWebView!!.settings.domStorageEnabled)
1373                     Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show()
1374                 else
1375                     Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show()
1376
1377                 // Reload the current WebView.
1378                 currentWebView!!.reload()
1379
1380                 // Consume the event.
1381                 true
1382             }
1383
1384             R.id.save_form_data -> {  // Form data.  This can be removed once the minimum API >= 26.
1385                 // Switch the status of saveFormDataEnabled.
1386                 @Suppress("DEPRECATION")
1387                 currentWebView!!.settings.saveFormData = !currentWebView!!.settings.saveFormData
1388
1389                 // Update the menu checkbox.
1390                 @Suppress("DEPRECATION")
1391                 menuItem.isChecked = currentWebView!!.settings.saveFormData
1392
1393                 // Display a snackbar.
1394                 @Suppress("DEPRECATION")
1395                 if (currentWebView!!.settings.saveFormData)
1396                     Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show()
1397                 else
1398                     Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show()
1399
1400                 // Update the privacy icon.
1401                 updatePrivacyIcons(true)
1402
1403                 // Reload the current WebView.
1404                 currentWebView!!.reload()
1405
1406                 // Consume the event.
1407                 true
1408             }
1409
1410             R.id.clear_cookies -> {  // Clear cookies.
1411                 // Create a snackbar.
1412                 Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1413                     .setAction(R.string.undo) {}  // Everything will be handled by `onDismissed()` below.
1414                     .addCallback(object : Snackbar.Callback() {
1415                         override fun onDismissed(snackbar: Snackbar, event: Int) {
1416                             if (event != DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1417                                 // Delete the cookies.
1418                                 cookieManager.removeAllCookies(null)
1419                             }
1420                         }
1421                     })
1422                     .show()
1423
1424                 // Consume the event.
1425                 true
1426             }
1427
1428             R.id.clear_dom_storage -> {  // Clear DOM storage.
1429                 // Create a snackbar.
1430                 Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1431                     .setAction(R.string.undo) {}  // Everything will be handled by `onDismissed()` below.
1432                     .addCallback(object : Snackbar.Callback() {
1433                         override fun onDismissed(snackbar: Snackbar, event: Int) {
1434                             if (event != DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1435                                 // Get a handle for the web storage.
1436                                 val webStorage = WebStorage.getInstance()
1437
1438                                 // Delete the DOM Storage.
1439                                 webStorage.deleteAllData()
1440
1441                                 // Initialize a handler to manually delete the DOM storage files and directories.
1442                                 val deleteDomStorageHandler = Handler(Looper.getMainLooper())
1443
1444                                 // Setup a runnable to manually delete the DOM storage files and directories.
1445                                 val deleteDomStorageRunnable = Runnable {
1446                                     try {
1447                                         // Get a handle for the runtime.
1448                                         val runtime = Runtime.getRuntime()
1449
1450                                         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1451                                         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1452                                         val privateDataDirectoryString = applicationInfo.dataDir
1453
1454                                         // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1455                                         val deleteLocalStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Local Storage/"))
1456
1457                                         // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1458                                         val deleteIndexProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/IndexedDB")
1459                                         val deleteQuotaManagerProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager")
1460                                         val deleteQuotaManagerJournalProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager-journal")
1461                                         val deleteDatabasesProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/databases")
1462
1463                                         // Wait for the processes to finish.
1464                                         deleteLocalStorageProcess.waitFor()
1465                                         deleteIndexProcess.waitFor()
1466                                         deleteQuotaManagerProcess.waitFor()
1467                                         deleteQuotaManagerJournalProcess.waitFor()
1468                                         deleteDatabasesProcess.waitFor()
1469                                     } catch (exception: Exception) {
1470                                         // Do nothing if an error is thrown.
1471                                     }
1472                                 }
1473
1474                                 // Manually delete the DOM storage files after 200 milliseconds.
1475                                 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200)
1476                             }
1477                         }
1478                     })
1479                     .show()
1480
1481                 // Consume the event.
1482                 true
1483             }
1484
1485             R.id.clear_form_data -> {  // Clear form data.  This can be remove once the minimum API >= 26.
1486                 // Create a snackbar.
1487                 Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1488                     .setAction(R.string.undo) {}  // Everything will be handled by `onDismissed()` below.
1489                     .addCallback(object : Snackbar.Callback() {
1490                         override fun onDismissed(snackbar: Snackbar, event: Int) {
1491                             if (event != DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1492                                 // Get a handle for the webView database.
1493                                 val webViewDatabase = WebViewDatabase.getInstance(applicationContext)
1494
1495                                 // Delete the form data.
1496                                 @Suppress("DEPRECATION")
1497                                 webViewDatabase.clearFormData()
1498                             }
1499                         }
1500                     })
1501                     .show()
1502
1503                 // Consume the event.
1504                 true
1505             }
1506
1507             R.id.easylist -> {  // EasyList.
1508                 // Toggle the EasyList status.
1509                 currentWebView!!.easyListEnabled = !currentWebView!!.easyListEnabled
1510
1511                 // Update the menu checkbox.
1512                 menuItem.isChecked = currentWebView!!.easyListEnabled
1513
1514                 // Reload the current WebView.
1515                 currentWebView!!.reload()
1516
1517                 // Consume the event.
1518                 true
1519             }
1520
1521             R.id.easyprivacy -> {  // EasyPrivacy.
1522                 // Toggle the EasyPrivacy status.
1523                 currentWebView!!.easyPrivacyEnabled = !currentWebView!!.easyPrivacyEnabled
1524
1525                 // Update the menu checkbox.
1526                 menuItem.isChecked = currentWebView!!.easyPrivacyEnabled
1527
1528                 // Reload the current WebView.
1529                 currentWebView!!.reload()
1530
1531                 // Consume the event.
1532                 true
1533             }
1534
1535             R.id.fanboys_annoyance_list -> {  // Fanboy's Annoyance List.
1536                 // Toggle Fanboy's Annoyance List status.
1537                 currentWebView!!.fanboysAnnoyanceListEnabled = !currentWebView!!.fanboysAnnoyanceListEnabled
1538
1539                 // Update the menu checkbox.
1540                 menuItem.isChecked = currentWebView!!.fanboysAnnoyanceListEnabled
1541
1542                 // Update the status of Fanboy's Social Blocking List.
1543                 optionsFanboysSocialBlockingListMenuItem.isEnabled = !currentWebView!!.fanboysAnnoyanceListEnabled
1544
1545                 // Reload the current WebView.
1546                 currentWebView!!.reload()
1547
1548                 // Consume the event.
1549                 true
1550             }
1551
1552             R.id.fanboys_social_blocking_list -> {  // Fanboy's Social Blocking List.
1553                 // Toggle Fanboy's Social Blocking List status.
1554                 currentWebView!!.fanboysSocialBlockingListEnabled = !currentWebView!!.fanboysSocialBlockingListEnabled
1555
1556                 // Update the menu checkbox.
1557                 menuItem.isChecked = currentWebView!!.fanboysSocialBlockingListEnabled
1558
1559                 // Reload the current WebView.
1560                 currentWebView!!.reload()
1561
1562                 // Consume the event.
1563                 true
1564             }
1565
1566             R.id.ultralist -> {  // UltraList.
1567                 // Toggle the UltraList status.
1568                 currentWebView!!.ultraListEnabled = !currentWebView!!.ultraListEnabled
1569
1570                 // Update the menu checkbox.
1571                 menuItem.isChecked = currentWebView!!.ultraListEnabled
1572
1573                 // Reload the current WebView.
1574                 currentWebView!!.reload()
1575
1576                 // Consume the event.
1577                 true
1578             }
1579
1580             R.id.ultraprivacy -> {  // UltraPrivacy.
1581                 // Toggle the UltraPrivacy status.
1582                 currentWebView!!.ultraPrivacyEnabled = !currentWebView!!.ultraPrivacyEnabled
1583
1584                 // Update the menu checkbox.
1585                 menuItem.isChecked = currentWebView!!.ultraPrivacyEnabled
1586
1587                 // Reload the current WebView.
1588                 currentWebView!!.reload()
1589
1590                 // Consume the event.
1591                 true
1592             }
1593
1594             R.id.block_all_third_party_requests -> {  // Block all third-party requests.
1595                 //Toggle the third-party requests blocker status.
1596                 currentWebView!!.blockAllThirdPartyRequests = !currentWebView!!.blockAllThirdPartyRequests
1597
1598                 // Update the menu checkbox.
1599                 menuItem.isChecked = currentWebView!!.blockAllThirdPartyRequests
1600
1601                 // Reload the current WebView.
1602                 currentWebView!!.reload()
1603
1604                 // Consume the event.
1605                 true
1606             }
1607
1608             R.id.proxy_none -> {  // Proxy - None.
1609                 // Update the proxy mode.
1610                 proxyMode = ProxyHelper.NONE
1611
1612                 // Apply the proxy mode.
1613                 applyProxy(true)
1614
1615                 // Consume the event.
1616                 true
1617             }
1618
1619             R.id.proxy_tor -> {  // Proxy - Tor.
1620                 // Update the proxy mode.
1621                 proxyMode = ProxyHelper.TOR
1622
1623                 // Apply the proxy mode.
1624                 applyProxy(true)
1625
1626                 // Consume the event.
1627                 true
1628             }
1629
1630             R.id.proxy_i2p -> {  // Proxy - I2P.
1631                 // Update the proxy mode.
1632                 proxyMode = ProxyHelper.I2P
1633
1634                 // Apply the proxy mode.
1635                 applyProxy(true)
1636
1637                 // Consume the event.
1638                 true
1639             }
1640
1641             R.id.proxy_custom -> {  // Proxy - Custom.
1642                 // Update the proxy mode.
1643                 proxyMode = ProxyHelper.CUSTOM
1644
1645                 // Apply the proxy mode.
1646                 applyProxy(true)
1647
1648                 // Consume the event.
1649                 true
1650             }
1651
1652             R.id.user_agent_privacy_browser -> {  // User Agent - Privacy Browser.
1653                 // Update the user agent.
1654                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[0]
1655
1656                 // Reload the current WebView.
1657                 currentWebView!!.reload()
1658
1659                 // Consume the event.
1660                 true
1661             }
1662
1663             R.id.user_agent_webview_default -> {  // User Agent - WebView Default.
1664                 // Update the user agent.
1665                 currentWebView!!.settings.userAgentString = ""
1666
1667                 // Reload the current WebView.
1668                 currentWebView!!.reload()
1669
1670                 // Consume the event.
1671                 true
1672             }
1673
1674             R.id.user_agent_firefox_on_android -> {  // User Agent - Firefox on Android.
1675                 // Update the user agent.
1676                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[2]
1677
1678                 // Reload the current WebView.
1679                 currentWebView!!.reload()
1680
1681                 // Consume the event.
1682                 true
1683             }
1684
1685             R.id.user_agent_chrome_on_android -> {  // User Agent - Chrome on Android.
1686                 // Update the user agent.
1687                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[3]
1688
1689                 // Reload the current WebView.
1690                 currentWebView!!.reload()
1691
1692                 // Consume the event.
1693                 true
1694             }
1695
1696             R.id.user_agent_safari_on_ios -> {  // User Agent - Safari on iOS.
1697                 // Update the user agent.
1698                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[4]
1699
1700                 // Reload the current WebView.
1701                 currentWebView!!.reload()
1702
1703                 // Consume the event.
1704                 true
1705             }
1706
1707             R.id.user_agent_firefox_on_linux -> {  // User Agent - Firefox on Linux.
1708                 // Update the user agent.
1709                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[5]
1710
1711                 // Reload the current WebView.
1712                 currentWebView!!.reload()
1713
1714                 // Consume the event.
1715                 true
1716             }
1717
1718             R.id.user_agent_chromium_on_linux -> {  // User Agent - Chromium on Linux.
1719                 // Update the user agent.
1720                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[6]
1721
1722                 // Reload the current WebView.
1723                 currentWebView!!.reload()
1724
1725                 // Consume the event.
1726                 true
1727             }
1728
1729             R.id.user_agent_firefox_on_windows -> {  // User Agent - Firefox on Windows.
1730                 // Update the user agent.
1731                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[7]
1732
1733                 // Reload the current WebView.
1734                 currentWebView!!.reload()
1735
1736                 // Consume the event.
1737                 true
1738             }
1739
1740             R.id.user_agent_chrome_on_windows -> {  // User Agent - Chrome on Windows.
1741                 // Update the user agent.
1742                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[8]
1743
1744                 // Reload the current WebView.
1745                 currentWebView!!.reload()
1746
1747                 // Consume the event.
1748                 true
1749             }
1750
1751             R.id.user_agent_edge_on_windows -> {  // User Agent - Edge on Windows.
1752                 // Update the user agent.
1753                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[9]
1754
1755                 // Reload the current WebView.
1756                 currentWebView!!.reload()
1757
1758                 // Consume the event.
1759                 true
1760             }
1761
1762             R.id.user_agent_internet_explorer_on_windows -> {  // User Agent - Internet Explorer on Windows.
1763                 // Update the user agent.
1764                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[10]
1765
1766                 // Reload the current WebView.
1767                 currentWebView!!.reload()
1768
1769                 // Consume the event.
1770                 true
1771             }
1772
1773             R.id.user_agent_safari_on_macos -> {  // User Agent - Safari on macOS.
1774                 // Update the user agent.
1775                 currentWebView!!.settings.userAgentString = resources.getStringArray(R.array.user_agent_data)[11]
1776
1777                 // Reload the current WebView.
1778                 currentWebView!!.reload()
1779
1780                 // Consume the event.
1781                 true
1782             }
1783
1784             R.id.user_agent_custom -> {  // User Agent - Custom.
1785                 // Update the user agent.
1786                 currentWebView!!.settings.userAgentString = sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))
1787
1788                 // Reload the current WebView.
1789                 currentWebView!!.reload()
1790
1791                 // Consume the event.
1792                 true
1793             }
1794
1795             R.id.font_size -> {  // Font size.
1796                 // Instantiate the font size dialog.
1797                 val fontSizeDialogFragment: DialogFragment = FontSizeDialog.displayDialog(currentWebView!!.settings.textZoom)
1798
1799                 // Show the font size dialog.
1800                 fontSizeDialogFragment.show(supportFragmentManager, getString(R.string.font_size))
1801
1802                 // Consume the event.
1803                 true
1804             }
1805
1806             R.id.swipe_to_refresh -> {  // Swipe to refresh.
1807                 // Toggle the stored status of swipe to refresh.
1808                 currentWebView!!.swipeToRefresh = !currentWebView!!.swipeToRefresh
1809
1810                 // Update the swipe refresh layout.
1811                 if (currentWebView!!.swipeToRefresh)  // Swipe to refresh is enabled.  Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
1812                     swipeRefreshLayout.isEnabled = currentWebView!!.scrollY == 0
1813                 else  // Swipe to refresh is disabled.
1814                     swipeRefreshLayout.isEnabled = false
1815
1816                 // Consume the event.
1817                 true
1818             }
1819
1820             R.id.wide_viewport -> {  // Wide viewport.
1821                 // Toggle the viewport.
1822                 currentWebView!!.settings.useWideViewPort = !currentWebView!!.settings.useWideViewPort
1823
1824                 // Consume the event.
1825                 true
1826             }
1827
1828             R.id.display_images -> {  // Display images.
1829                 // Toggle the displaying of images.
1830                 if (currentWebView!!.settings.loadsImagesAutomatically) {  // Images are currently loaded automatically.
1831                     // Disable loading of images.
1832                     currentWebView!!.settings.loadsImagesAutomatically = false
1833
1834                     // Reload the website to remove existing images.
1835                     currentWebView!!.reload()
1836                 } else {  // Images are not currently loaded automatically.
1837                     // Enable loading of images.  Missing images will be loaded without the need for a reload.
1838                     currentWebView!!.settings.loadsImagesAutomatically = true
1839                 }
1840
1841                 // Consume the event.
1842                 true
1843             }
1844
1845             R.id.dark_webview -> {  // Dark WebView.
1846                 // Toggle dark WebView if supported.
1847                 if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING))
1848                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(currentWebView!!.settings, !WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView!!.settings)
1849                 )
1850
1851                 // Consume the event.
1852                 true
1853             }
1854
1855             R.id.find_on_page -> {  // Find on page.
1856                 // Set the minimum height of the find on page linear layout to match the toolbar.
1857                 findOnPageLinearLayout.minimumHeight = toolbar.height
1858
1859                 // Hide the toolbar.
1860                 toolbar.visibility = View.GONE
1861
1862                 // Show the find on page linear layout.
1863                 findOnPageLinearLayout.visibility = View.VISIBLE
1864
1865                 // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
1866                 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1867                 findOnPageEditText.postDelayed({
1868                     // Set the focus on the find on page edit text.
1869                     findOnPageEditText.requestFocus()
1870
1871                     // Get a handle for the input method manager.
1872                     val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
1873
1874                     // Display the keyboard.  `0` sets no input flags.
1875                     inputMethodManager.showSoftInput(findOnPageEditText, 0)
1876                 }, 200)
1877
1878                 // Consume the event.
1879                 true
1880             }
1881
1882             R.id.print -> {  // Print.
1883                 // Get a print manager instance.
1884                 val printManager = (getSystemService(PRINT_SERVICE) as PrintManager)
1885
1886                 // Create a print document adapter from the current WebView.
1887                 val printDocumentAdapter = currentWebView!!.createPrintDocumentAdapter(getString(R.string.print))
1888
1889                 // Print the document.
1890                 printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null)
1891
1892                 // Consume the event.
1893                 true
1894             }
1895
1896             R.id.save_url -> {  // Save URL.
1897                 // Check the download preference.
1898                 if (downloadWithExternalApp)  // Download with an external app.
1899                     downloadUrlWithExternalApp(currentWebView!!.currentUrl)
1900                 else  // Handle the download inside of Privacy Browser.  The dialog will be displayed once the file size and the content disposition have been acquired.
1901                     PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, currentWebView!!.currentUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies)
1902
1903                 // Consume the event.
1904                 true
1905             }
1906
1907             R.id.save_archive -> {
1908                 // Open the file picker with a default file name built from the current domain name.
1909                 saveWebpageArchiveActivityResultLauncher.launch(currentWebView!!.currentDomainName + ".mht")
1910
1911                 // Consume the event.
1912                 true
1913             }
1914
1915             R.id.save_image -> {  // Save image.
1916                 // Open the file picker with a default file name built from the current domain name.
1917                 saveWebpageImageActivityResultLauncher.launch(currentWebView!!.currentDomainName + ".png")
1918
1919                 // Consume the event.
1920                 true
1921             }
1922
1923             R.id.add_to_homescreen -> {  // Add to homescreen.
1924                 // Instantiate the create home screen shortcut dialog.
1925                 val createHomeScreenShortcutDialogFragment: DialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView!!.title!!, currentWebView!!.url!!, currentWebView!!.getFavoriteIcon())
1926
1927                 // Show the create home screen shortcut dialog.
1928                 createHomeScreenShortcutDialogFragment.show(supportFragmentManager, getString(R.string.create_shortcut))
1929
1930                 // Consume the event.
1931                 true
1932             }
1933
1934             R.id.view_source -> {  // View source.
1935                 // Create an intent to launch the view source activity.
1936                 val viewSourceIntent = Intent(this, ViewSourceActivity::class.java)
1937
1938                 // Add the variables to the intent.
1939                 viewSourceIntent.putExtra(CURRENT_URL, currentWebView!!.url)
1940                 viewSourceIntent.putExtra(USER_AGENT, currentWebView!!.settings.userAgentString)
1941
1942                 // Make it so.
1943                 startActivity(viewSourceIntent)
1944
1945                 // Consume the event.
1946                 true
1947             }
1948
1949             R.id.share_message -> {  // Share a message.
1950                 // Prepare the share string.
1951                 val shareString = currentWebView!!.title + " â€“ " + currentWebView!!.url
1952
1953                 // Create the share intent.
1954                 val shareMessageIntent = Intent(Intent.ACTION_SEND)
1955
1956                 // Add the share string to the intent.
1957                 shareMessageIntent.putExtra(Intent.EXTRA_TEXT, shareString)
1958
1959                 // Set the MIME type.
1960                 shareMessageIntent.type = "text/plain"
1961
1962                 // Set the intent to open in a new task.
1963                 shareMessageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
1964
1965                 // Make it so.
1966                 startActivity(Intent.createChooser(shareMessageIntent, getString(R.string.share_message)))
1967
1968                 // Consume the event.
1969                 true
1970             }
1971
1972             R.id.share_url -> {  // Share URL.
1973                 // Create the share intent.
1974                 val shareUrlIntent = Intent(Intent.ACTION_SEND)
1975
1976                 // Add the URL to the intent.
1977                 shareUrlIntent.putExtra(Intent.EXTRA_TEXT, currentWebView!!.url)
1978
1979                 // Add the title to the intent.
1980                 shareUrlIntent.putExtra(Intent.EXTRA_SUBJECT, currentWebView!!.title)
1981
1982                 // Set the MIME type.
1983                 shareUrlIntent.type = "text/plain"
1984
1985                 // Set the intent to open in a new task.
1986                 shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
1987
1988                 //Make it so.
1989                 startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)))
1990
1991                 // Consume the event.
1992                 true
1993             }
1994
1995             R.id.open_with_app -> {  // Open with app.
1996                 // Open the URL with an outside app.
1997                 openWithApp(currentWebView!!.url!!)
1998
1999                 // Consume the event.
2000                 true
2001             }
2002
2003             R.id.open_with_browser -> {  // Open with browser.
2004                 // Open the URL with an outside browser.
2005                 openWithBrowser(currentWebView!!.url!!)
2006
2007                 // Consume the event.
2008                 true
2009             }
2010
2011             R.id.add_or_edit_domain -> {  // Add or edit domain.
2012                 // Reapply the domain settings on returning to `MainWebViewActivity`.
2013                 reapplyDomainSettingsOnRestart = true
2014
2015                 // Check if domain settings are currently applied.
2016                 if (currentWebView!!.domainSettingsApplied) {  // Edit the current domain settings.
2017                     // Create an intent to launch the domains activity.
2018                     val domainsIntent = Intent(this, DomainsActivity::class.java)
2019
2020                     // Add the extra information to the intent.
2021                     domainsIntent.putExtra(LOAD_DOMAIN, currentWebView!!.domainSettingsDatabaseId)
2022                     domainsIntent.putExtra(CLOSE_ON_BACK, true)
2023                     domainsIntent.putExtra(CURRENT_URL, currentWebView!!.url)
2024                     domainsIntent.putExtra(CURRENT_IP_ADDRESSES, currentWebView!!.currentIpAddresses)
2025
2026                     // Get the current certificate.
2027                     val sslCertificate = currentWebView!!.certificate
2028
2029                     // Check to see if the SSL certificate is populated.
2030                     if (sslCertificate != null) {
2031                         // Extract the certificate to strings.
2032                         val issuedToCName = sslCertificate.issuedTo.cName
2033                         val issuedToOName = sslCertificate.issuedTo.oName
2034                         val issuedToUName = sslCertificate.issuedTo.uName
2035                         val issuedByCName = sslCertificate.issuedBy.cName
2036                         val issuedByOName = sslCertificate.issuedBy.oName
2037                         val issuedByUName = sslCertificate.issuedBy.uName
2038                         val startDateLong = sslCertificate.validNotBeforeDate.time
2039                         val endDateLong = sslCertificate.validNotAfterDate.time
2040
2041                         // Add the certificate to the intent.
2042                         domainsIntent.putExtra(SSL_ISSUED_TO_CNAME, issuedToCName)
2043                         domainsIntent.putExtra(SSL_ISSUED_TO_ONAME, issuedToOName)
2044                         domainsIntent.putExtra(SSL_ISSUED_TO_UNAME, issuedToUName)
2045                         domainsIntent.putExtra(SSL_ISSUED_BY_CNAME, issuedByCName)
2046                         domainsIntent.putExtra(SSL_ISSUED_BY_ONAME, issuedByOName)
2047                         domainsIntent.putExtra(SSL_ISSUED_BY_UNAME, issuedByUName)
2048                         domainsIntent.putExtra(SSL_START_DATE, startDateLong)
2049                         domainsIntent.putExtra(SSL_END_DATE, endDateLong)
2050                     }
2051
2052                     // Make it so.
2053                     startActivity(domainsIntent)
2054                 } else {  // Add a new domain.
2055                     // Get the current URI.
2056                     val currentUri = Uri.parse(currentWebView!!.url)
2057
2058                     // Get the current domain from the URI.  Use an empty string if it is null.
2059                     val currentDomain = currentUri.host?: ""
2060
2061                     // Create the domain and store the database ID.
2062                     val newDomainDatabaseId = domainsDatabaseHelper!!.addDomain(currentDomain)
2063
2064                     // Create an intent to launch the domains activity.
2065                     val domainsIntent = Intent(this, DomainsActivity::class.java)
2066
2067                     // Add the extra information to the intent.
2068                     domainsIntent.putExtra(LOAD_DOMAIN, newDomainDatabaseId)
2069                     domainsIntent.putExtra(CLOSE_ON_BACK, true)
2070                     domainsIntent.putExtra(CURRENT_URL, currentWebView!!.url)
2071                     domainsIntent.putExtra(CURRENT_IP_ADDRESSES, currentWebView!!.currentIpAddresses)
2072
2073                     // Get the current certificate.
2074                     val sslCertificate = currentWebView!!.certificate
2075
2076                     // Check to see if the SSL certificate is populated.
2077                     if (sslCertificate != null) {
2078                         // Extract the certificate to strings.
2079                         val issuedToCName = sslCertificate.issuedTo.cName
2080                         val issuedToOName = sslCertificate.issuedTo.oName
2081                         val issuedToUName = sslCertificate.issuedTo.uName
2082                         val issuedByCName = sslCertificate.issuedBy.cName
2083                         val issuedByOName = sslCertificate.issuedBy.oName
2084                         val issuedByUName = sslCertificate.issuedBy.uName
2085                         val startDateLong = sslCertificate.validNotBeforeDate.time
2086                         val endDateLong = sslCertificate.validNotAfterDate.time
2087
2088                         // Add the certificate to the intent.
2089                         domainsIntent.putExtra(SSL_ISSUED_TO_CNAME, issuedToCName)
2090                         domainsIntent.putExtra(SSL_ISSUED_TO_ONAME, issuedToOName)
2091                         domainsIntent.putExtra(SSL_ISSUED_TO_UNAME, issuedToUName)
2092                         domainsIntent.putExtra(SSL_ISSUED_BY_CNAME, issuedByCName)
2093                         domainsIntent.putExtra(SSL_ISSUED_BY_ONAME, issuedByOName)
2094                         domainsIntent.putExtra(SSL_ISSUED_BY_UNAME, issuedByUName)
2095                         domainsIntent.putExtra(SSL_START_DATE, startDateLong)
2096                         domainsIntent.putExtra(SSL_END_DATE, endDateLong)
2097                     }
2098
2099                     // Make it so.
2100                     startActivity(domainsIntent)
2101                 }
2102
2103                 // Consume the event.
2104                 true
2105             }
2106
2107             else -> {  // There is no match with the options menu.  Pass the event up to the parent method.
2108                 // Don't consume the event.
2109                 super.onOptionsItemSelected(menuItem)
2110             }
2111         }
2112     }
2113
2114     override fun onNavigationItemSelected(menuItem: MenuItem): Boolean {
2115         // Run the commands that correspond to the selected menu item.
2116         when (menuItem.itemId) {
2117             R.id.clear_and_exit -> {  // Clear and exit.
2118                 // Clear and exit Privacy Browser.
2119                 clearAndExit()
2120             }
2121
2122             R.id.home -> {  // Home.
2123                 // Load the homepage.
2124                 loadUrl(currentWebView!!, sharedPreferences.getString(getString(R.string.homepage_key), getString(R.string.homepage_default_value))!!)
2125             }
2126
2127             R.id.back -> {  // Back.
2128                 // Check if the WebView can go back.
2129                 if (currentWebView!!.canGoBack()) {
2130                     // Get the current web back forward list.
2131                     val webBackForwardList = currentWebView!!.copyBackForwardList()
2132
2133                     // Get the previous entry URL.
2134                     val previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.currentIndex - 1).url
2135
2136                     // Apply the domain settings.
2137                     applyDomainSettings(currentWebView!!, previousUrl, resetTab = false, reloadWebsite = false, loadUrl = false)
2138
2139                     // Load the previous website in the history.
2140                     currentWebView!!.goBack()
2141                 }
2142             }
2143
2144             R.id.forward -> {  // Forward.
2145                 // Check if the WebView can go forward.
2146                 if (currentWebView!!.canGoForward()) {
2147                     // Get the current web back forward list.
2148                     val webBackForwardList = currentWebView!!.copyBackForwardList()
2149
2150                     // Get the next entry URL.
2151                     val nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.currentIndex + 1).url
2152
2153                     // Apply the domain settings.
2154                     applyDomainSettings(currentWebView!!, nextUrl, resetTab = false, reloadWebsite = false, loadUrl = false)
2155
2156                     // Load the next website in the history.
2157                     currentWebView!!.goForward()
2158                 }
2159             }
2160
2161             R.id.history -> {  // History.
2162                 // Instantiate the URL history dialog.
2163                 val urlHistoryDialogFragment: DialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView!!.webViewFragmentId)
2164
2165                 // Show the URL history dialog.
2166                 urlHistoryDialogFragment.show(supportFragmentManager, getString(R.string.history))
2167             }
2168
2169             R.id.open -> {  // Open.
2170                 // Instantiate the open file dialog.
2171                 val openDialogFragment: DialogFragment = OpenDialog()
2172
2173                 // Show the open file dialog.
2174                 openDialogFragment.show(supportFragmentManager, getString(R.string.open))
2175             }
2176
2177             R.id.requests -> {  // Requests.
2178                 // Populate the resource requests.
2179                 RequestsActivity.resourceRequests = currentWebView!!.getResourceRequests()
2180
2181                 // Create an intent to launch the Requests activity.
2182                 val requestsIntent = Intent(this, RequestsActivity::class.java)
2183
2184                 // Add the block third-party requests status to the intent.
2185                 requestsIntent.putExtra(BLOCK_ALL_THIRD_PARTY_REQUESTS, currentWebView!!.blockAllThirdPartyRequests)
2186
2187                 // Make it so.
2188                 startActivity(requestsIntent)
2189             }
2190
2191             R.id.downloads -> {  // Downloads.
2192                 // Try the default system download manager.
2193                 try {
2194                     // Launch the default system Download Manager.
2195                     val defaultDownloadManagerIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
2196
2197                     // Launch as a new task so that the download manager and Privacy Browser show as separate windows in the recent tasks list.
2198                     defaultDownloadManagerIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
2199
2200                     // Make it so.
2201                     startActivity(defaultDownloadManagerIntent)
2202                 } catch (defaultDownloadManagerException: Exception) {  // The system download manager is not available.
2203                     // Try a generic file manager.
2204                     try {
2205                         // Create a generic file manager intent.
2206                         val genericFileManagerIntent = Intent(Intent.ACTION_VIEW)
2207
2208                         // Open the download directory.
2209                         genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR)
2210
2211                         // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2212                         genericFileManagerIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
2213
2214                         // Make it so.
2215                         startActivity(genericFileManagerIntent)
2216                     } catch (genericFileManagerException: Exception) {  // A generic file manager is not available.
2217                         // Try an alternate file manager.
2218                         try {
2219                             // Create an alternate file manager intent.
2220                             val alternateFileManagerIntent = Intent(Intent.ACTION_VIEW)
2221
2222                             // Open the download directory.
2223                             alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder")
2224
2225                             // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2226                             alternateFileManagerIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
2227
2228                             // Open the alternate file manager.
2229                             startActivity(alternateFileManagerIntent)
2230                         } catch (alternateFileManagerException: Exception) {
2231                             // Display a snackbar.
2232                             Snackbar.make(currentWebView!!, R.string.no_file_manager_detected, Snackbar.LENGTH_INDEFINITE).show()
2233                         }
2234                     }
2235                 }
2236             }
2237
2238             R.id.domains -> {  // Domains.
2239                 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2240                 reapplyDomainSettingsOnRestart = true
2241
2242                 // Create a domains activity intent.
2243                 val domainsIntent = Intent(this, DomainsActivity::class.java)
2244
2245                 // Add the extra information to the intent.
2246                 domainsIntent.putExtra(CURRENT_URL, currentWebView!!.url)
2247                 domainsIntent.putExtra(CURRENT_IP_ADDRESSES, currentWebView!!.currentIpAddresses)
2248
2249                 // Get the current certificate.
2250                 val sslCertificate = currentWebView!!.certificate
2251
2252                 // Check to see if the SSL certificate is populated.
2253                 if (sslCertificate != null) {
2254                     // Extract the certificate to strings.
2255                     val issuedToCName = sslCertificate.issuedTo.cName
2256                     val issuedToOName = sslCertificate.issuedTo.oName
2257                     val issuedToUName = sslCertificate.issuedTo.uName
2258                     val issuedByCName = sslCertificate.issuedBy.cName
2259                     val issuedByOName = sslCertificate.issuedBy.oName
2260                     val issuedByUName = sslCertificate.issuedBy.uName
2261                     val startDateLong = sslCertificate.validNotBeforeDate.time
2262                     val endDateLong = sslCertificate.validNotAfterDate.time
2263
2264                     // Add the certificate to the intent.
2265                     domainsIntent.putExtra(SSL_ISSUED_TO_CNAME, issuedToCName)
2266                     domainsIntent.putExtra(SSL_ISSUED_TO_ONAME, issuedToOName)
2267                     domainsIntent.putExtra(SSL_ISSUED_TO_UNAME, issuedToUName)
2268                     domainsIntent.putExtra(SSL_ISSUED_BY_CNAME, issuedByCName)
2269                     domainsIntent.putExtra(SSL_ISSUED_BY_ONAME, issuedByOName)
2270                     domainsIntent.putExtra(SSL_ISSUED_BY_UNAME, issuedByUName)
2271                     domainsIntent.putExtra(SSL_START_DATE, startDateLong)
2272                     domainsIntent.putExtra(SSL_END_DATE, endDateLong)
2273                 }
2274
2275                 // Make it so.
2276                 startActivity(domainsIntent)
2277             }
2278
2279             R.id.settings -> {  // Settings.
2280                 // Set the reapply on restart flags.
2281                 reapplyAppSettingsOnRestart = true
2282                 reapplyDomainSettingsOnRestart = true
2283
2284                 // Create a settings intent.
2285                 val settingsIntent = Intent(this, SettingsActivity::class.java)
2286
2287                 // Make it so.
2288                 startActivity(settingsIntent)
2289             }
2290
2291             R.id.import_export -> { // Import/Export.
2292                 // Create an intent to launch the import/export activity.
2293                 val importExportIntent = Intent(this, ImportExportActivity::class.java)
2294
2295                 // Make it so.
2296                 startActivity(importExportIntent)
2297             }
2298
2299             R.id.logcat -> {  // Logcat.
2300                 // Create an intent to launch the logcat activity.
2301                 val logcatIntent = Intent(this, LogcatActivity::class.java)
2302
2303                 // Make it so.
2304                 startActivity(logcatIntent)
2305             }
2306
2307             R.id.webview_devtools -> {  // WebView DevTools.
2308                 // Create a WebView DevTools intent.
2309                 val webViewDevToolsIntent = Intent("com.android.webview.SHOW_DEV_UI")
2310
2311                 // Launch as a new task so that the WebView DevTools and Privacy Browser show as a separate windows in the recent tasks list.
2312                 webViewDevToolsIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
2313
2314                 // Make it so.
2315                 startActivity(webViewDevToolsIntent)
2316             }
2317
2318             R.id.guide -> {  // Guide.
2319                 // Create an intent to launch the guide activity.
2320                 val guideIntent = Intent(this, GuideActivity::class.java)
2321
2322                 // Make it so.
2323                 startActivity(guideIntent)
2324             }
2325
2326             R.id.about -> {  // About
2327                 // Create an intent to launch the about activity.
2328                 val aboutIntent = Intent(this, AboutActivity::class.java)
2329
2330                 // Create a string array for the blocklist versions.
2331                 val blocklistVersions = arrayOf(easyList[0][0][0], easyPrivacy[0][0][0], fanboysAnnoyanceList[0][0][0], fanboysSocialList[0][0][0], ultraList[0][0][0], ultraPrivacy!![0][0][0])
2332
2333                 // Add the blocklist versions to the intent.
2334                 aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions)
2335
2336                 // Make it so.
2337                 startActivity(aboutIntent)
2338             }
2339         }
2340
2341         // Close the navigation drawer.
2342         drawerLayout.closeDrawer(GravityCompat.START)
2343
2344         // Return true.
2345         return true
2346     }
2347
2348     override fun onCreateContextMenu(contextMenu: ContextMenu, view: View, contextMenuInfo: ContextMenu.ContextMenuInfo?) {
2349         // Get the hit test result.
2350         val hitTestResult = currentWebView!!.hitTestResult
2351
2352         // Define the URL strings.
2353         val imageUrl: String?
2354         val linkUrl: String?
2355
2356         // Get a handle for the clipboard manager.
2357         val clipboardManager = (getSystemService(CLIPBOARD_SERVICE) as ClipboardManager)
2358
2359         // Process the link according to the type.
2360         when (hitTestResult.type) {
2361             // `SRC_ANCHOR_TYPE` is a link.
2362             WebView.HitTestResult.SRC_ANCHOR_TYPE -> {
2363                 // Get the target URL.
2364                 linkUrl = hitTestResult.extra!!
2365
2366                 // Set the target URL as the context menu title.
2367                 contextMenu.setHeaderTitle(linkUrl)
2368
2369                 // Add an open in new tab entry.
2370                 contextMenu.add(R.string.open_in_new_tab).setOnMenuItemClickListener {
2371                     // Load the link URL in a new tab and move to it.
2372                     addNewTab(linkUrl, true)
2373
2374                     // Consume the event.
2375                     true
2376                 }
2377
2378                 // Add an open in background entry.
2379                 contextMenu.add(R.string.open_in_background).setOnMenuItemClickListener {
2380                     // Load the link URL in a new tab but do not move to it.
2381                     addNewTab(linkUrl, false)
2382
2383                     // Consume the event.
2384                     true
2385                 }
2386
2387                 // Add an open with app entry.
2388                 contextMenu.add(R.string.open_with_app).setOnMenuItemClickListener {
2389                     // Open the URL with another app.
2390                     openWithApp(linkUrl)
2391
2392                     // Consume the event.
2393                     true
2394                 }
2395
2396                 // Add an open with browser entry.
2397                 contextMenu.add(R.string.open_with_browser).setOnMenuItemClickListener {
2398                     // Open the URL with another browser.
2399                     openWithBrowser(linkUrl)
2400
2401                     // Consume the event.
2402                     true
2403                 }
2404
2405                 // Add a copy URL entry.
2406                 contextMenu.add(R.string.copy_url).setOnMenuItemClickListener {
2407                     // Save the link URL in a clip data.
2408                     val srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl)
2409
2410                     // Set the clip data as the clipboard's primary clip.
2411                     clipboardManager.setPrimaryClip(srcAnchorTypeClipData)
2412
2413                     // Consume the event.
2414                     true
2415                 }
2416
2417                 // Add a Save URL entry.
2418                 contextMenu.add(R.string.save_url).setOnMenuItemClickListener {
2419                     // Check the download preference.
2420                     if (downloadWithExternalApp)  // Download with an external app.
2421                         downloadUrlWithExternalApp(linkUrl)
2422                     else  // Handle the download inside of Privacy Browser.  The dialog will be displayed once the file size and the content disposition have been acquired.
2423                         PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, linkUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies)
2424
2425                     // Consume the event.
2426                     true
2427                 }
2428
2429                 // Add an empty cancel entry, which by default closes the context menu.
2430                 contextMenu.add(R.string.cancel)
2431             }
2432
2433             // `IMAGE_TYPE` is an image.
2434             WebView.HitTestResult.IMAGE_TYPE -> {
2435                 // Get the image URL.
2436                 imageUrl = hitTestResult.extra!!
2437
2438                 // Set the context menu title.
2439                 if (imageUrl.startsWith("data:"))  // The image data is contained in within the URL, making it exceedingly long.  Truncate the image URL before making it the title.
2440                     contextMenu.setHeaderTitle(imageUrl.substring(0, 100))
2441                 else  // The image URL does not contain the full image data.  Set the image URL as the title of the context menu.
2442                     contextMenu.setHeaderTitle(imageUrl)
2443
2444                 // Add an open in new tab entry.
2445                 contextMenu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener {
2446                     // Load the image in a new tab.
2447                     addNewTab(imageUrl, true)
2448
2449                     // Consume the event.
2450                     true
2451                 }
2452
2453                 // Add an open with app entry.
2454                 contextMenu.add(R.string.open_with_app).setOnMenuItemClickListener {
2455                     // Open the image URL with an external app.
2456                     openWithApp(imageUrl)
2457
2458                     // Consume the event.
2459                     true
2460                 }
2461
2462                 // Add an open with browser entry.
2463                 contextMenu.add(R.string.open_with_browser).setOnMenuItemClickListener {
2464                     // Open the image URL with an external browser.
2465                     openWithBrowser(imageUrl)
2466
2467                     // Consume the event.
2468                     true
2469                 }
2470
2471                 // Add a view image entry.
2472                 contextMenu.add(R.string.view_image).setOnMenuItemClickListener {
2473                     // Load the image in the current tab.
2474                     loadUrl(currentWebView!!, imageUrl)
2475
2476                     // Consume the event.
2477                     true
2478                 }
2479
2480                 // Add a save image entry.
2481                 contextMenu.add(R.string.save_image).setOnMenuItemClickListener {
2482                     // Check the download preference.
2483                     if (downloadWithExternalApp) {  // Download with an external app.
2484                         downloadUrlWithExternalApp(imageUrl)
2485                     } else {  // Handle the download inside of Privacy Browser.  The dialog will be displayed once the file size and the content disposition have been acquired.
2486                         PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, imageUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies)
2487                     }
2488
2489                     // Consume the event.
2490                     true
2491                 }
2492
2493                 // Add a copy URL entry.
2494                 contextMenu.add(R.string.copy_url).setOnMenuItemClickListener {
2495                     // Save the image URL in a clip data.
2496                     val imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl)
2497
2498                     // Set the clip data as the clipboard's primary clip.
2499                     clipboardManager.setPrimaryClip(imageTypeClipData)
2500
2501                     // Consume the event.
2502                     true
2503                 }
2504
2505                 // Add an empty cancel entry, which by default closes the context menu.
2506                 contextMenu.add(R.string.cancel)
2507             }
2508
2509             // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2510             WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE -> {
2511                 // Get the image URL.
2512                 imageUrl = hitTestResult.extra!!
2513
2514                 // Instantiate a handler.
2515                 val handler = Handler(Looper.getMainLooper())
2516
2517                 // Get a handle for the handler message.
2518                 val message = handler.obtainMessage()
2519
2520                 // Request the image details from the last touched node be returned in the message.
2521                 currentWebView!!.requestFocusNodeHref(message)
2522
2523                 // Get the link URL from the message data.
2524                 linkUrl = message.data.getString("url")!!
2525
2526                 // Set the link URL as the title of the context menu.
2527                 contextMenu.setHeaderTitle(linkUrl)
2528
2529                 // Add an open in new tab entry.
2530                 contextMenu.add(R.string.open_in_new_tab).setOnMenuItemClickListener {
2531                     // Load the link URL in a new tab and move to it.
2532                     addNewTab(linkUrl, true)
2533
2534                     // Consume the event.
2535                     true
2536                 }
2537
2538                 // Add an open in background entry.
2539                 contextMenu.add(R.string.open_in_background).setOnMenuItemClickListener {
2540                     // Lod the link URL in a new tab but do not move to it.
2541                     addNewTab(linkUrl, false)
2542
2543                     // Consume the event.
2544                     true
2545                 }
2546
2547                 // Add an open image in new tab entry.
2548                 contextMenu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener {
2549                     // Load the image in a new tab and move to it.
2550                     addNewTab(imageUrl, true)
2551
2552                     // Consume the event.
2553                     true
2554                 }
2555
2556                 // Add an open with app entry.
2557                 contextMenu.add(R.string.open_with_app).setOnMenuItemClickListener {
2558                     // Open the link URL with an external app.
2559                     openWithApp(linkUrl)
2560
2561                     // Consume the event.
2562                     true
2563                 }
2564
2565                 // Add an open with browser entry.
2566                 contextMenu.add(R.string.open_with_browser).setOnMenuItemClickListener {
2567                     // Open the link URL with an external browser.
2568                     openWithBrowser(linkUrl)
2569
2570                     // Consume the event.
2571                     true
2572                 }
2573
2574                 // Add a view image entry.
2575                 contextMenu.add(R.string.view_image).setOnMenuItemClickListener {
2576                     // View the image in the current tab.
2577                     loadUrl(currentWebView!!, imageUrl)
2578
2579                     // Consume the event.
2580                     true
2581                 }
2582
2583                 // Add a Save Image entry.
2584                 contextMenu.add(R.string.save_image).setOnMenuItemClickListener {
2585                     // Check the download preference.
2586                     if (downloadWithExternalApp)  // Download with an external app.
2587                         downloadUrlWithExternalApp(imageUrl)
2588                     else  // Handle the download inside of Privacy Browser.  The dialog will be displayed once the file size and the content disposition have been acquired.
2589                         PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, imageUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies)
2590
2591                     // Consume the event.
2592                     true
2593                 }
2594
2595                 // Add a copy URL entry.
2596                 contextMenu.add(R.string.copy_url).setOnMenuItemClickListener {
2597                     // Save the link URL in a clip data.
2598                     val srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl)
2599
2600                     // Set the clip data as the clipboard's primary clip.
2601                     clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData)
2602
2603                     // Consume the event.
2604                     true
2605                 }
2606
2607                 // Add a save URL entry.
2608                 contextMenu.add(R.string.save_url).setOnMenuItemClickListener {
2609                     // Check the download preference.
2610                     if (downloadWithExternalApp)  // Download with an external app.
2611                         downloadUrlWithExternalApp(linkUrl)
2612                     else  // Handle the download inside of Privacy Browser.  The dialog will be displayed once the file size and the content disposition have been acquired.
2613                         PrepareSaveDialogCoroutine.prepareSaveDialog(this, supportFragmentManager, linkUrl, currentWebView!!.settings.userAgentString, currentWebView!!.acceptCookies)
2614
2615                     // Consume the event.
2616                     true
2617                 }
2618
2619                 // Add an empty cancel entry, which by default closes the context menu.
2620                 contextMenu.add(R.string.cancel)
2621             }
2622
2623             WebView.HitTestResult.EMAIL_TYPE -> {
2624                 // Get the target URL.
2625                 linkUrl = hitTestResult.extra
2626
2627                 // Set the target URL as the title of the context menu.
2628                 contextMenu.setHeaderTitle(linkUrl)
2629
2630                 // Add a write email entry.
2631                 contextMenu.add(R.string.write_email).setOnMenuItemClickListener {
2632                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2633                     val emailIntent = Intent(Intent.ACTION_SENDTO)
2634
2635                     // Parse the url and set it as the data for the intent.
2636                     emailIntent.data = Uri.parse("mailto:$linkUrl")
2637
2638                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2639                     emailIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
2640
2641                     try {
2642                         // Make it so.
2643                         startActivity(emailIntent)
2644                     } catch (exception: ActivityNotFoundException) {
2645                         // Display a snackbar.
2646                         Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
2647                     }
2648
2649                     // Consume the event.
2650                     true
2651                 }
2652
2653                 // Add a copy email address entry.
2654                 contextMenu.add(R.string.copy_email_address).setOnMenuItemClickListener {
2655                     // Save the email address in a clip data.
2656                     val srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl)
2657
2658                     // Set the clip data as the clipboard's primary clip.
2659                     clipboardManager.setPrimaryClip(srcEmailTypeClipData)
2660
2661                     // Consume the event.
2662                     true
2663                 }
2664
2665                 // Add an empty cancel entry, which by default closes the context menu.
2666                 contextMenu.add(R.string.cancel)
2667             }
2668         }
2669     }
2670
2671     // The view parameter cannot be removed because it is called from the layout onClick.
2672     fun addTab(@Suppress("UNUSED_PARAMETER")view: View?) {
2673         // Add a new tab with a blank URL.
2674         addNewTab("", true)
2675     }
2676
2677     private fun addNewTab(urlString: String, moveToTab: Boolean) {
2678         // Clear the focus from the URL edit text, so that it will be populated with the information from the new tab.
2679         urlEditText.clearFocus()
2680
2681         // Get the new page number.  The page numbers are 0 indexed, so the new page number will match the current count.
2682         val newTabNumber = tabLayout.tabCount
2683
2684         // Add a new tab.
2685         tabLayout.addTab(tabLayout.newTab())
2686
2687         // Get the new tab.
2688         val newTab = tabLayout.getTabAt(newTabNumber)!!
2689
2690         // Set a custom view on the new tab.
2691         newTab.setCustomView(R.layout.tab_custom_view)
2692
2693         // Add the new WebView page.
2694         webViewPagerAdapter!!.addPage(newTabNumber, webViewPager, urlString, moveToTab)
2695
2696         // Show the app bar if it is at the bottom of the screen and the new tab is taking focus.
2697         if (bottomAppBar && moveToTab && appBarLayout.translationY != 0f) {
2698             // Animate the bottom app bar onto the screen.
2699             objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0f)
2700
2701             // Make it so.
2702             objectAnimator.start()
2703         }
2704     }
2705
2706     private fun applyAppSettings() {
2707         // Store the values from the shared preferences in variables.
2708         incognitoModeEnabled = sharedPreferences.getBoolean(getString(R.string.incognito_mode_key), false)
2709         sanitizeTrackingQueries = sharedPreferences.getBoolean(getString(R.string.tracking_queries_key), true)
2710         sanitizeAmpRedirects = sharedPreferences.getBoolean(getString(R.string.amp_redirects_key), true)
2711         proxyMode = sharedPreferences.getString(getString(R.string.proxy_key), getString(R.string.proxy_default_value))!!
2712         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean(getString(R.string.full_screen_browsing_mode_key), false)
2713         hideAppBar = sharedPreferences.getBoolean(getString(R.string.hide_app_bar_key), true)
2714         downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false)
2715         scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), true)
2716
2717         // Apply the saved proxy mode if the app has been restarted.
2718         if (savedProxyMode != null) {
2719             // Apply the saved proxy mode.
2720             proxyMode = savedProxyMode!!
2721
2722             // Reset the saved proxy mode.
2723             savedProxyMode = null
2724         }
2725
2726         // Get the search string.
2727         val searchString = sharedPreferences.getString(getString(R.string.search_key), getString(R.string.search_default_value))!!
2728
2729         // Set the search string, using the custom search URL if specified.
2730         searchURL = if (searchString == getString(R.string.custom_url_item))
2731             sharedPreferences.getString(getString(R.string.search_custom_url_key), getString(R.string.search_custom_url_default_value))!!
2732         else
2733             searchString
2734
2735         // Apply the proxy.
2736         applyProxy(false)
2737
2738         // Adjust the layout and scrolling parameters according to the position of the app bar.
2739         if (bottomAppBar) {  // The app bar is on the bottom.
2740             // Adjust the UI.
2741             if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
2742                 // Reset the WebView padding to fill the available space.
2743                 swipeRefreshLayout.setPadding(0, 0, 0, 0)
2744             } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
2745                 // Move the WebView above the app bar layout.
2746                 swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight)
2747
2748                 // Show the app bar if it is scrolled off the screen.
2749                 if (appBarLayout.translationY != 0f) {
2750                     // Animate the bottom app bar onto the screen.
2751                     objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0f)
2752
2753                     // Make it so.
2754                     objectAnimator.start()
2755                 }
2756             }
2757         } else {  // The app bar is on the top.
2758             // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
2759             val swipeRefreshLayoutParams = swipeRefreshLayout.layoutParams as CoordinatorLayout.LayoutParams
2760             val toolbarLayoutParams = toolbar.layoutParams as AppBarLayout.LayoutParams
2761             val findOnPageLayoutParams = findOnPageLinearLayout.layoutParams as AppBarLayout.LayoutParams
2762             val tabsLayoutParams = tabsLinearLayout.layoutParams as AppBarLayout.LayoutParams
2763
2764             // Add the scrolling behavior to the layout parameters.
2765             if (scrollAppBar) {
2766                 // Enable scrolling of the app bar.
2767                 swipeRefreshLayoutParams.behavior = AppBarLayout.ScrollingViewBehavior()
2768                 toolbarLayoutParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS or AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
2769                 findOnPageLayoutParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS or AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
2770                 tabsLayoutParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS or AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
2771             } else {
2772                 // Disable scrolling of the app bar.
2773                 swipeRefreshLayoutParams.behavior = null
2774                 toolbarLayoutParams.scrollFlags = 0
2775                 findOnPageLayoutParams.scrollFlags = 0
2776                 tabsLayoutParams.scrollFlags = 0
2777
2778                 // Expand the app bar if it is currently collapsed.
2779                 appBarLayout.setExpanded(true)
2780             }
2781
2782             // Set the app bar scrolling for each WebView.
2783             for (i in 0 until webViewPagerAdapter!!.count) {
2784                 // Get the WebView tab fragment.
2785                 val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
2786
2787                 // Get the fragment view.
2788                 val fragmentView = webViewTabFragment.view
2789
2790                 // Only modify the WebViews if they exist.
2791                 if (fragmentView != null) {
2792                     // Get the nested scroll WebView from the tab fragment.
2793                     val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
2794
2795                     // Set the app bar scrolling.
2796                     nestedScrollWebView.isNestedScrollingEnabled = scrollAppBar
2797                 }
2798             }
2799         }
2800
2801         // Update the full screen browsing mode settings.
2802         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
2803             // Update the visibility of the app bar, which might have changed in the settings.
2804             if (hideAppBar) {
2805                 // Hide the tab linear layout.
2806                 tabsLinearLayout.visibility = View.GONE
2807
2808                 // Hide the app bar.
2809                 appBar.hide()
2810             } else {
2811                 // Show the tab linear layout.
2812                 tabsLinearLayout.visibility = View.VISIBLE
2813
2814                 // Show the app bar.
2815                 appBar.show()
2816             }
2817
2818             /* Hide the system bars.
2819              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2820              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
2821              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2822              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2823              */
2824
2825             // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
2826             @Suppress("DEPRECATION")
2827             rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
2828         } else {  // Privacy Browser is not in full screen browsing mode.
2829             // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
2830             inFullScreenBrowsingMode = false
2831
2832             // Show the tab linear layout.
2833             tabsLinearLayout.visibility = View.VISIBLE
2834
2835             // Show the app bar.
2836             appBar.show()
2837
2838             // Remove the `SYSTEM_UI` flags from the root frame layout.  The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
2839             @Suppress("DEPRECATION")
2840             rootFrameLayout.systemUiVisibility = 0
2841         }
2842     }
2843
2844     // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
2845     @SuppressLint("SetJavaScriptEnabled")
2846     private fun applyDomainSettings(nestedScrollWebView: NestedScrollWebView, url: String?, resetTab: Boolean, reloadWebsite: Boolean, loadUrl: Boolean) {
2847         // Store the current URL.
2848         nestedScrollWebView.currentUrl = url!!
2849
2850         // Parse the URL into a URI.
2851         val uri = Uri.parse(url)
2852
2853         // Extract the domain from the URI.
2854         var newHostName = uri.host
2855
2856         // Strings don't like to be null.
2857         if (newHostName == null)
2858             newHostName = ""
2859
2860         // Apply the domain settings if a new domain is being loaded or if the new domain is blank.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
2861         if (nestedScrollWebView.currentDomainName != newHostName || newHostName == "") {
2862             // Set the new host name as the current domain name.
2863             nestedScrollWebView.currentDomainName = newHostName
2864
2865             // Reset the ignoring of pinned domain information.
2866             nestedScrollWebView.ignorePinnedDomainInformation = false
2867
2868             // Clear any pinned SSL certificate or IP addresses.
2869             nestedScrollWebView.clearPinnedSslCertificate()
2870             nestedScrollWebView.pinnedIpAddresses = ""
2871
2872             // Reset the favorite icon if specified.
2873             if (resetTab) {
2874                 // Initialize the favorite icon.
2875                 nestedScrollWebView.initializeFavoriteIcon()
2876
2877                 // Get the current page position.
2878                 val currentPagePosition = webViewPagerAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
2879
2880                 // Get the corresponding tab.
2881                 val tab = tabLayout.getTabAt(currentPagePosition)
2882
2883                 // Update the tab if it isn't null, which sometimes happens when restarting from the background.
2884                 if (tab != null) {
2885                     // Get the tab custom view.
2886                     val tabCustomView = tab.customView!!
2887
2888                     // Get the tab views.
2889                     val tabFavoriteIconImageView = tabCustomView.findViewById<ImageView>(R.id.favorite_icon_imageview)
2890                     val tabTitleTextView = tabCustomView.findViewById<TextView>(R.id.title_textview)
2891
2892                     // Set the default favorite icon as the favorite icon for this tab.
2893                     tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteIcon(), 64, 64, true))
2894
2895                     // Set the loading title text.
2896                     tabTitleTextView.setText(R.string.loading)
2897                 }
2898             }
2899
2900             // Initialize the domain name in database variable.
2901             var domainNameInDatabase: String? = null
2902
2903             // Check the hostname against the domain settings set.
2904             if (domainsSettingsSet.contains(newHostName)) {  // The hostname is contained in the domain settings set.
2905                 // Record the domain name in the database.
2906                 domainNameInDatabase = newHostName
2907
2908                 // Set the domain settings applied tracker to true.
2909                 nestedScrollWebView.domainSettingsApplied = true
2910             } else {  // The hostname is not contained in the domain settings set.
2911                 // Set the domain settings applied tracker to false.
2912                 nestedScrollWebView.domainSettingsApplied = false
2913             }
2914
2915             // Check all the subdomains of the host name against wildcard domains in the domain cursor.
2916             while (!nestedScrollWebView.domainSettingsApplied && newHostName!!.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the hostname.
2917                 if (domainsSettingsSet.contains("*.$newHostName")) {  // Check the host name prepended by `*.`.
2918                     // Set the domain settings applied tracker to true.
2919                     nestedScrollWebView.domainSettingsApplied = true
2920
2921                     // Store the applied domain names as it appears in the database.
2922                     domainNameInDatabase = "*.$newHostName"
2923                 }
2924
2925                 // Strip out the lowest subdomain of of the host name.
2926                 newHostName = newHostName.substring(newHostName.indexOf(".") + 1)
2927             }
2928
2929             // Store the general preference information.
2930             val defaultFontSizeString = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value))
2931             val defaultUserAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value))
2932             val defaultSwipeToRefresh = sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true)
2933             val webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value))
2934             val wideViewport = sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true)
2935             val displayWebpageImages = sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true)
2936
2937             // Get the WebView theme entry values string array.
2938             val webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values)
2939
2940             // Initialize the user agent array adapter and string array.
2941             val userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item)
2942             val userAgentDataArray = resources.getStringArray(R.array.user_agent_data)
2943
2944             // Apply either the domain settings for the default settings.
2945             if (nestedScrollWebView.domainSettingsApplied) {  // The url has custom domain settings.
2946                 // Get a cursor for the current host.
2947                 val currentDomainSettingsCursor = domainsDatabaseHelper!!.getCursorForDomainName(domainNameInDatabase!!)
2948
2949                 // Move to the first position.
2950                 currentDomainSettingsCursor.moveToFirst()
2951
2952                 // Get the settings from the cursor.
2953                 nestedScrollWebView.domainSettingsDatabaseId = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID))
2954                 nestedScrollWebView.settings.javaScriptEnabled = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1
2955                 nestedScrollWebView.acceptCookies = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)) == 1
2956                 nestedScrollWebView.settings.domStorageEnabled = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1
2957                 // Form data can be removed once the minimum API >= 26.
2958                 val saveFormData = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1
2959                 nestedScrollWebView.easyListEnabled = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1
2960                 nestedScrollWebView.easyPrivacyEnabled = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1
2961                 nestedScrollWebView.fanboysAnnoyanceListEnabled = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1
2962                 nestedScrollWebView.fanboysSocialBlockingListEnabled =
2963                     currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1
2964                 nestedScrollWebView.ultraListEnabled = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)) == 1
2965                 nestedScrollWebView.ultraPrivacyEnabled = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1
2966                 nestedScrollWebView.blockAllThirdPartyRequests = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1
2967                 val userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT))
2968                 val fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE))
2969                 val swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH))
2970                 val webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME))
2971                 val wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT))
2972                 val displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES))
2973                 val pinnedSslCertificate = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1
2974                 val pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME))
2975                 val pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION))
2976                 val pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT))
2977                 val pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME))
2978                 val pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION))
2979                 val pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT))
2980                 val pinnedSslStartDate = Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)))
2981                 val pinnedSslEndDate = Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)))
2982                 val pinnedIpAddresses = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1
2983                 val pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES))
2984
2985                 // Close the current host domain settings cursor.
2986                 currentDomainSettingsCursor.close()
2987
2988                 // If there is a pinned SSL certificate, store it in the WebView.
2989                 if (pinnedSslCertificate)
2990                     nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName,
2991                         pinnedSslStartDate, pinnedSslEndDate)
2992
2993                 // If there is a pinned IP address, store it in the WebView.
2994                 if (pinnedIpAddresses)
2995                     nestedScrollWebView.pinnedIpAddresses = pinnedHostIpAddresses
2996
2997                 // Apply the cookie domain settings.
2998                 cookieManager.setAcceptCookie(nestedScrollWebView.acceptCookies)
2999
3000                 // Apply the form data setting if the API < 26.
3001                 @Suppress("DEPRECATION")
3002                 if (Build.VERSION.SDK_INT < 26)
3003                     nestedScrollWebView.settings.saveFormData = saveFormData
3004
3005                 // Apply the font size.
3006                 try {  // Try the specified font size to see if it is valid.
3007                     if (fontSize == 0) {  // Apply the default font size.
3008                         // Set the font size from the value in the app settings.
3009                         nestedScrollWebView.settings.textZoom = defaultFontSizeString!!.toInt()
3010                     } else {  // Apply the font size from domain settings.
3011                         nestedScrollWebView.settings.textZoom = fontSize
3012                     }
3013                 } catch (exception: Exception) {  // The specified font size is invalid
3014                     // Set the font size to be 100%
3015                     nestedScrollWebView.settings.textZoom = 100
3016                 }
3017
3018                 // Set the user agent.
3019                 if (userAgentName == getString(R.string.system_default_user_agent)) {  // Use the system default user agent.
3020                     // Set the user agent according to the system default.
3021                     when (val defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName)) {
3022                         // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
3023                         UNRECOGNIZED_USER_AGENT -> nestedScrollWebView.settings.userAgentString = defaultUserAgentName
3024
3025                         // Set the user agent to `""`, which uses the default value.
3026                         SETTINGS_WEBVIEW_DEFAULT_USER_AGENT -> nestedScrollWebView.settings.userAgentString = ""
3027
3028                         // Set the default custom user agent.
3029                         SETTINGS_CUSTOM_USER_AGENT -> nestedScrollWebView.settings.userAgentString =
3030                             sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))
3031
3032                         // Get the user agent string from the user agent data array
3033                         else -> nestedScrollWebView.settings.userAgentString = userAgentDataArray[defaultUserAgentArrayPosition]
3034                     }
3035                 } else {  // Set the user agent according to the stored name.
3036                     // Set the user agent.
3037                     when (val userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName)) {
3038                         // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
3039                         UNRECOGNIZED_USER_AGENT ->
3040                             nestedScrollWebView.settings.userAgentString = userAgentName
3041
3042                         // Set the user agent to `""`, which uses the default value.
3043                         SETTINGS_WEBVIEW_DEFAULT_USER_AGENT ->
3044                             nestedScrollWebView.settings.userAgentString = ""
3045
3046                         // Get the user agent string from the user agent data array.
3047                         else ->
3048                             nestedScrollWebView.settings.userAgentString = userAgentDataArray[userAgentArrayPosition]
3049                     }
3050                 }
3051
3052                 // Set swipe to refresh.
3053                 when (swipeToRefreshInt) {
3054                     DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
3055                         // Store the swipe to refresh status in the nested scroll WebView.
3056                         nestedScrollWebView.swipeToRefresh = defaultSwipeToRefresh
3057
3058                         // Update the swipe refresh layout.
3059                         if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
3060                             // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null).
3061                             if (currentWebView != null) {
3062                                 // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
3063                                 swipeRefreshLayout.isEnabled = (currentWebView!!.scrollY == 0)
3064                             }
3065                         } else {  // Swipe to refresh is disabled.
3066                             // Disable the swipe refresh layout.
3067                             swipeRefreshLayout.isEnabled = false
3068                         }
3069                     }
3070
3071                     DomainsDatabaseHelper.ENABLED -> {
3072                         // Store the swipe to refresh status in the nested scroll WebView.
3073                         nestedScrollWebView.swipeToRefresh = true
3074
3075                         // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null).
3076                         if (currentWebView != null) {
3077                             // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
3078                             swipeRefreshLayout.isEnabled = (currentWebView!!.scrollY == 0)
3079                         }
3080                     }
3081
3082                     DomainsDatabaseHelper.DISABLED -> {
3083                         // Store the swipe to refresh status in the nested scroll WebView.
3084                         nestedScrollWebView.swipeToRefresh = false
3085
3086                         // Disable swipe to refresh.
3087                         swipeRefreshLayout.isEnabled = false
3088                     }
3089                 }
3090
3091                 // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported.
3092                 if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
3093                     // Set the WebView theme.
3094                     when (webViewThemeInt) {
3095                         // Set the WebView theme.
3096                         DomainsDatabaseHelper.SYSTEM_DEFAULT ->
3097                             when (webViewTheme) {
3098                                 // The light theme is selected.  Turn off algorithmic darkening.
3099                                 webViewThemeEntryValuesStringArray[1] -> WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
3100
3101                                 // The dark theme is selected.  Turn on algorithmic darkening.
3102                                 webViewThemeEntryValuesStringArray[2] -> WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
3103
3104                                 // The system default theme is selected.
3105                                 else -> {
3106                                     // Get the current system theme status.
3107                                     val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
3108
3109                                     // Set the algorithmic darkening according to the current system theme status.
3110                                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)
3111                                 }
3112                             }
3113
3114                         // Turn off algorithmic darkening.
3115                         DomainsDatabaseHelper.LIGHT_THEME -> WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
3116
3117                         // Turn on algorithmic darkening.
3118                         DomainsDatabaseHelper.DARK_THEME -> WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
3119                     }
3120                 }
3121
3122                 // Set the wide viewport status.
3123                 when (wideViewportInt) {
3124                     DomainsDatabaseHelper.SYSTEM_DEFAULT -> nestedScrollWebView.settings.useWideViewPort = wideViewport
3125                     DomainsDatabaseHelper.ENABLED -> nestedScrollWebView.settings.useWideViewPort = true
3126                     DomainsDatabaseHelper.DISABLED -> nestedScrollWebView.settings.useWideViewPort = false
3127                 }
3128
3129                 // Set the display webpage images status.
3130                 when (displayWebpageImagesInt) {
3131                     DomainsDatabaseHelper.SYSTEM_DEFAULT -> nestedScrollWebView.settings.loadsImagesAutomatically = displayWebpageImages
3132                     DomainsDatabaseHelper.ENABLED -> nestedScrollWebView.settings.loadsImagesAutomatically = true
3133                     DomainsDatabaseHelper.DISABLED -> nestedScrollWebView.settings.loadsImagesAutomatically = false
3134                 }
3135
3136                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
3137                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.drawable.domain_settings_url_background)
3138             } else {  // The new URL does not have custom domain settings.  Load the defaults.
3139                 // Store the values from the shared preferences.
3140                 nestedScrollWebView.settings.javaScriptEnabled = sharedPreferences.getBoolean(getString(R.string.javascript_key), false)
3141                 nestedScrollWebView.acceptCookies = sharedPreferences.getBoolean(getString(R.string.cookies_key), false)
3142                 nestedScrollWebView.settings.domStorageEnabled = sharedPreferences.getBoolean(getString(R.string.dom_storage_key), false)
3143                 val saveFormData = sharedPreferences.getBoolean(getString(R.string.save_form_data_key), false) // Form data can be removed once the minimum API >= 26.
3144                 nestedScrollWebView.easyListEnabled = sharedPreferences.getBoolean(getString(R.string.easylist_key), true)
3145                 nestedScrollWebView.easyPrivacyEnabled = sharedPreferences.getBoolean(getString(R.string.easyprivacy_key), true)
3146                 nestedScrollWebView.fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean(getString(R.string.fanboys_annoyance_list_key), true)
3147                 nestedScrollWebView.fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean(getString(R.string.fanboys_social_blocking_list_key), true)
3148                 nestedScrollWebView.ultraListEnabled = sharedPreferences.getBoolean(getString(R.string.ultralist_key), true)
3149                 nestedScrollWebView.ultraPrivacyEnabled = sharedPreferences.getBoolean(getString(R.string.ultraprivacy_key), true)
3150                 nestedScrollWebView.blockAllThirdPartyRequests = sharedPreferences.getBoolean(getString(R.string.block_all_third_party_requests_key), false)
3151
3152                 // Apply the default cookie setting.
3153                 cookieManager.setAcceptCookie(nestedScrollWebView.acceptCookies)
3154
3155                 // Apply the default font size setting.
3156                 try {
3157                     // Try to set the font size from the value in the app settings.
3158                     nestedScrollWebView.settings.textZoom = defaultFontSizeString!!.toInt()
3159                 } catch (exception: Exception) {
3160                     // If the app settings value is invalid, set the font size to 100%.
3161                     nestedScrollWebView.settings.textZoom = 100
3162                 }
3163
3164                 // Apply the form data setting if the API < 26.
3165                 if (Build.VERSION.SDK_INT < 26)
3166                     @Suppress("DEPRECATION")
3167                     nestedScrollWebView.settings.saveFormData = saveFormData
3168
3169                 // Store the swipe to refresh status in the nested scroll WebView.
3170                 nestedScrollWebView.swipeToRefresh = defaultSwipeToRefresh
3171
3172                 // Update the swipe refresh layout.
3173                 if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
3174                     // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null).
3175                     if (currentWebView != null) {
3176                         // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
3177                         swipeRefreshLayout.isEnabled = currentWebView!!.scrollY == 0
3178                     }
3179                 } else {  // Swipe to refresh is disabled.
3180                     // Disable the swipe refresh layout.
3181                     swipeRefreshLayout.isEnabled = false
3182                 }
3183
3184                 // Reset the domain settings database ID.
3185                 nestedScrollWebView.domainSettingsDatabaseId = -1
3186
3187                 // Set the user agent.
3188                 when (val userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName)) {
3189                     // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
3190                     UNRECOGNIZED_USER_AGENT -> nestedScrollWebView.settings.userAgentString = defaultUserAgentName
3191
3192                     // Set the user agent to `""`, which uses the default value.
3193                     SETTINGS_WEBVIEW_DEFAULT_USER_AGENT -> nestedScrollWebView.settings.userAgentString = ""
3194
3195                     // Set the default custom user agent.
3196                     SETTINGS_CUSTOM_USER_AGENT -> nestedScrollWebView.settings.userAgentString =
3197                         sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))
3198
3199                     // Get the user agent string from the user agent data array
3200                     else -> nestedScrollWebView.settings.userAgentString = userAgentDataArray[userAgentArrayPosition]
3201                 }
3202
3203                 // Set the WebView theme if the device is running API >= 29 and algorithmic darkening is supported.
3204                 if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
3205                     // Set the WebView theme.
3206                     when (webViewTheme) {
3207                         // The light theme is selected.  Turn off algorithmic darkening.
3208                         webViewThemeEntryValuesStringArray[1] -> WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
3209
3210                         // The dark theme is selected.  Turn on algorithmic darkening.
3211                         webViewThemeEntryValuesStringArray[2] -> WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
3212
3213                         // The system default theme is selected.  Get the current system theme status.
3214                         else -> {
3215                             // Get the current theme status.
3216                             val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
3217
3218                             // Set the algorithmic darkening according to the current system theme status.
3219                             WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)
3220                         }
3221                     }
3222                 }
3223
3224                 // Set the viewport.
3225                 nestedScrollWebView.settings.useWideViewPort = wideViewport
3226
3227                 // Set the loading of webpage images.
3228                 nestedScrollWebView.settings.loadsImagesAutomatically = displayWebpageImages
3229
3230                 // Set a transparent background on the URL relative layout.
3231                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.color.transparent)
3232             }
3233
3234             // Update the privacy icons.
3235             updatePrivacyIcons(true)
3236         }
3237
3238         // Reload the website if returning from the Domains activity.
3239         if (reloadWebsite)
3240             nestedScrollWebView.reload()
3241
3242         // Load the URL if directed.  This makes sure that the domain settings are properly loaded before the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
3243         if (loadUrl)
3244             nestedScrollWebView.loadUrl(url)
3245     }
3246
3247     private fun applyProxy(reloadWebViews: Boolean) {
3248         // Set the proxy according to the mode.
3249         proxyHelper.setProxy(applicationContext, appBarLayout, proxyMode)
3250
3251         // Reset the waiting for proxy tracker.
3252         waitingForProxy = false
3253
3254         // Set the proxy.
3255         when (proxyMode) {
3256             ProxyHelper.NONE -> {
3257                 // Initialize a color background typed value.
3258                 val colorBackgroundTypedValue = TypedValue()
3259
3260                 // Get the color background from the theme.
3261                 theme.resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true)
3262
3263                 // Get the color background int from the typed value.
3264                 val colorBackgroundInt = colorBackgroundTypedValue.data
3265
3266                 // Set the default app bar layout background.
3267                 appBarLayout.setBackgroundColor(colorBackgroundInt)
3268             }
3269
3270             ProxyHelper.TOR -> {
3271                 // Set the app bar background to indicate proxying is enabled.
3272                 appBarLayout.setBackgroundResource(R.color.blue_background)
3273
3274                 // Check to see if Orbot is installed.
3275                 try {
3276                     // Get the package manager.
3277                     val packageManager = packageManager
3278
3279                     // Check to see if Orbot is in the list.  This will throw an error and drop to the catch section if it isn't installed.  The deprecated method must be used until the minimum API >= 33.
3280                     @Suppress("DEPRECATION")
3281                     packageManager.getPackageInfo("org.torproject.android", 0)
3282
3283                     // Check to see if the proxy is ready.
3284                     if (orbotStatus != ProxyHelper.ORBOT_STATUS_ON) {  // Orbot is not ready.
3285                         // Set the waiting for proxy status.
3286                         waitingForProxy = true
3287
3288                         // Show the waiting for proxy dialog if it isn't already displayed.
3289                         if (supportFragmentManager.findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) {
3290                             // Get a handle for the waiting for proxy alert dialog.
3291                             val waitingForProxyDialogFragment = WaitingForProxyDialog()
3292
3293                             // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
3294                             try {
3295                                 // Show the waiting for proxy alert dialog.
3296                                 waitingForProxyDialogFragment.show(supportFragmentManager, getString(R.string.waiting_for_proxy_dialog))
3297                             } catch (waitingForTorException: Exception) {
3298                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
3299                                 pendingDialogsArrayList.add(PendingDialogDataClass(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)))
3300                             }
3301                         }
3302                     }
3303                 } catch (exception: PackageManager.NameNotFoundException) {  // Orbot is not installed.
3304                     // Show the Orbot not installed dialog if it is not already displayed.
3305                     if (supportFragmentManager.findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
3306                         // Get a handle for the Orbot not installed alert dialog.
3307                         val orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode)
3308
3309                         // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
3310                         try {
3311                             // Display the Orbot not installed alert dialog.
3312                             orbotNotInstalledDialogFragment.show(supportFragmentManager, getString(R.string.proxy_not_installed_dialog))
3313                         } catch (orbotNotInstalledException: Exception) {
3314                             // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
3315                             pendingDialogsArrayList.add(PendingDialogDataClass(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)))
3316                         }
3317                     }
3318                 }
3319             }
3320
3321             ProxyHelper.I2P -> {
3322                 // Set the app bar background to indicate proxying is enabled.
3323                 appBarLayout.setBackgroundResource(R.color.blue_background)
3324
3325                 // Check to see if I2P is installed.
3326                 try {
3327                     // Check to see if the F-Droid flavor is installed.  This will throw an error and drop to the catch section if it isn't installed.
3328                     // The deprecated method must be used until the minimum API >= 33.
3329                     @Suppress("DEPRECATION")
3330                     packageManager.getPackageInfo("net.i2p.android.router", 0)
3331                 } catch (fdroidException: PackageManager.NameNotFoundException) {  // The F-Droid flavor is not installed.
3332                     try {
3333                         // Check to see if the Google Play flavor is installed.  This will throw an error and drop to the catch section if it isn't installed.
3334                         // The deprecated method must be used until the minimum API >= 33.
3335                         @Suppress("DEPRECATION")
3336                         packageManager.getPackageInfo("net.i2p.android", 0)
3337                     } catch (googlePlayException: PackageManager.NameNotFoundException) {  // The Google Play flavor is not installed.
3338                         // Sow the I2P not installed dialog if it is not already displayed.
3339                         if (supportFragmentManager.findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
3340                             // Get a handle for the waiting for proxy alert dialog.
3341                             val i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode)
3342
3343                             // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
3344                             try {
3345                                 // Display the I2P not installed alert dialog.
3346                                 i2pNotInstalledDialogFragment.show(supportFragmentManager, getString(R.string.proxy_not_installed_dialog))
3347                             } catch (i2pNotInstalledException: Exception) {
3348                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
3349                                 pendingDialogsArrayList.add(PendingDialogDataClass(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)))
3350                             }
3351                         }
3352                     }
3353                 }
3354             }
3355
3356             ProxyHelper.CUSTOM ->
3357                 // Set the app bar background to indicate proxying is enabled.
3358                 appBarLayout.setBackgroundResource(R.color.blue_background)
3359         }
3360
3361         // Reload the WebViews if requested and not waiting for the proxy.
3362         if (reloadWebViews && !waitingForProxy) {
3363             // Reload the WebViews.
3364             for (i in 0 until webViewPagerAdapter!!.count) {
3365                 // Get the WebView tab fragment.
3366                 val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
3367
3368                 // Get the fragment view.
3369                 val fragmentView = webViewTabFragment.view
3370
3371                 // Only reload the WebViews if they exist.
3372                 if (fragmentView != null) {
3373                     // Get the nested scroll WebView from the tab fragment.
3374                     val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
3375
3376                     // Reload the WebView.
3377                     nestedScrollWebView.reload()
3378                 }
3379             }
3380         }
3381     }
3382
3383     // The view parameter cannot be removed because it is called from the layout onClick.
3384     fun bookmarksBack(@Suppress("UNUSED_PARAMETER")view: View?) {
3385         if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
3386             // close the bookmarks drawer.
3387             drawerLayout.closeDrawer(GravityCompat.END)
3388         } else {  // A subfolder is displayed.
3389             // Set the former parent folder as the current folder.
3390             currentBookmarksFolder = bookmarksDatabaseHelper!!.getParentFolderName(currentBookmarksFolder)
3391
3392             // Load the new folder.
3393             loadBookmarksFolder()
3394         }
3395     }
3396
3397     private fun clearAndExit() {
3398         // Close the bookmarks cursor if it exists.
3399         bookmarksCursor?.close()
3400
3401         // Close the databases helpers if they exist.
3402         bookmarksDatabaseHelper?.close()
3403         domainsDatabaseHelper?.close()
3404
3405         // Get the status of the clear everything preference.
3406         val clearEverything = sharedPreferences.getBoolean(getString(R.string.clear_everything_key), true)
3407
3408         // Get a handle for the runtime.
3409         val runtime = Runtime.getRuntime()
3410
3411         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
3412         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
3413         val privateDataDirectoryString = applicationInfo.dataDir
3414
3415         // Clear cookies.
3416         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cookies_key), true)) {
3417             // Ass the cookie manager to delete all the cookies.
3418             cookieManager.removeAllCookies(null)
3419
3420             // Ask the cookie manager to flush the cookie database.
3421             cookieManager.flush()
3422
3423             // Manually delete the cookies database, as the cookie manager sometimes will not flush its changes to disk before system exit is run.
3424             try {
3425                 // Two commands must be used because `Runtime.exec()` does not like `*`.
3426                 val deleteCookiesProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/Cookies")
3427                 val deleteCookiesJournalProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/Cookies-journal")
3428
3429                 // Wait until the processes have finished.
3430                 deleteCookiesProcess.waitFor()
3431                 deleteCookiesJournalProcess.waitFor()
3432             } catch (exception: Exception) {
3433                 // Do nothing if an error is thrown.
3434             }
3435         }
3436
3437         // Clear DOM storage.
3438         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_dom_storage_key), true)) {
3439             // Ask web storage to clear the DOM storage.
3440             WebStorage.getInstance().deleteAllData()
3441
3442             // Manually delete the DOM storage files and directories, as web storage sometimes will not flush its changes to disk before system exit is run.
3443             try {
3444                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3445                 val deleteLocalStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Local Storage/"))
3446
3447                 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3448                 val deleteIndexProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/IndexedDB")
3449                 val deleteQuotaManagerProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager")
3450                 val deleteQuotaManagerJournalProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager-journal")
3451                 val deleteDatabaseProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/databases")
3452
3453                 // Wait until the processes have finished.
3454                 deleteLocalStorageProcess.waitFor()
3455                 deleteIndexProcess.waitFor()
3456                 deleteQuotaManagerProcess.waitFor()
3457                 deleteQuotaManagerJournalProcess.waitFor()
3458                 deleteDatabaseProcess.waitFor()
3459             } catch (exception: Exception) {
3460                 // Do nothing if an error is thrown.
3461             }
3462         }
3463
3464         // Clear form data if the API < 26.
3465         if (Build.VERSION.SDK_INT < 26 && (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_form_data_key), true))) {
3466             // Ask the WebView database to clear the form data.
3467             @Suppress("DEPRECATION")
3468             WebViewDatabase.getInstance(this).clearFormData()
3469
3470             // Manually delete the form data database, as the WebView database sometimes will not flush its changes to disk before system exit is run.
3471             try {
3472                 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
3473                 val deleteWebDataProcess = runtime.exec(arrayOf("rm", "-f", "$privateDataDirectoryString/app_webview/Web Data"))
3474                 val deleteWebDataJournalProcess = runtime.exec(arrayOf("rm", "-f", "$privateDataDirectoryString/app_webview/Web Data-journal"))
3475
3476                 // Wait until the processes have finished.
3477                 deleteWebDataProcess.waitFor()
3478                 deleteWebDataJournalProcess.waitFor()
3479             } catch (exception: Exception) {
3480                 // Do nothing if an error is thrown.
3481             }
3482         }
3483
3484         // Clear the logcat.
3485         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_logcat_key), true)) {
3486             try {
3487                 // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
3488                 val process = Runtime.getRuntime().exec("logcat -b all -c")
3489
3490                 // Wait for the process to finish.
3491                 process.waitFor()
3492             } catch (exception: IOException) {
3493                 // Do nothing.
3494             } catch (exception: InterruptedException) {
3495                 // Do nothing.
3496             }
3497         }
3498
3499         // Clear the cache.
3500         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cache_key), true)) {
3501             // Clear the cache from each WebView.
3502             for (i in 0 until webViewPagerAdapter!!.count) {
3503                 // Get the WebView tab fragment.
3504                 val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
3505
3506                 // Get the WebView fragment view.
3507                 val webViewFragmentView = webViewTabFragment.view
3508
3509                 // Only clear the cache if the WebView exists.
3510                 if (webViewFragmentView != null) {
3511                     // Get the nested scroll WebView from the tab fragment.
3512                     val nestedScrollWebView = webViewFragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
3513
3514                     // Clear the cache for this WebView.
3515                     nestedScrollWebView.clearCache(true)
3516                 }
3517             }
3518
3519             // Manually delete the cache directories.
3520             try {
3521                 // Delete the main cache directory.
3522                 val deleteCacheProcess = runtime.exec("rm -rf $privateDataDirectoryString/cache")
3523
3524                 // Delete the secondary `Service Worker` cache directory.
3525                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3526                 val deleteServiceWorkerProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Service Worker/"))
3527
3528                 // Wait until the processes have finished.
3529                 deleteCacheProcess.waitFor()
3530                 deleteServiceWorkerProcess.waitFor()
3531             } catch (exception: Exception) {
3532                 // Do nothing if an error is thrown.
3533             }
3534         }
3535
3536         // Wipe out each WebView.
3537         for (i in 0 until webViewPagerAdapter!!.count) {
3538             // Get the WebView tab fragment.
3539             val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
3540
3541             // Get the WebView frame layout.
3542             val webViewFrameLayout = webViewTabFragment.view as FrameLayout?
3543
3544             // Only wipe out the WebView if it exists.
3545             if (webViewFrameLayout != null) {
3546                 // Get the nested scroll WebView from the tab fragment.
3547                 val nestedScrollWebView = webViewFrameLayout.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
3548
3549                 // Clear SSL certificate preferences for this WebView.
3550                 nestedScrollWebView.clearSslPreferences()
3551
3552                 // Clear the back/forward history for this WebView.
3553                 nestedScrollWebView.clearHistory()
3554
3555                 // Remove all the views from the frame layout.
3556                 webViewFrameLayout.removeAllViews()
3557
3558                 // Destroy the internal state of the WebView.
3559                 nestedScrollWebView.destroy()
3560             }
3561         }
3562
3563         // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
3564         // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
3565         if (clearEverything) {
3566             try {
3567                 // Delete the folder.
3568                 val deleteAppWebviewProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview")
3569
3570                 // Wait until the process has finished.
3571                 deleteAppWebviewProcess.waitFor()
3572             } catch (exception: Exception) {
3573                 // Do nothing if an error is thrown.
3574             }
3575         }
3576
3577         // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3578         finishAndRemoveTask()
3579
3580         // Remove the terminated program from RAM.  The status code is `0`.
3581         exitProcess(0)
3582     }
3583
3584     // The view parameter cannot be removed because it is called from the layout onClick.
3585     fun closeFindOnPage(@Suppress("UNUSED_PARAMETER")view: View?) {
3586         // Delete the contents of the find on page edit text.
3587         findOnPageEditText.text = null
3588
3589         // Clear the highlighted phrases if the WebView is not null.
3590         currentWebView?.clearMatches()
3591
3592         // Hide the find on page linear layout.
3593         findOnPageLinearLayout.visibility = View.GONE
3594
3595         // Show the toolbar.
3596         toolbar.visibility = View.VISIBLE
3597
3598         // Get a handle for the input method manager.
3599         val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
3600
3601         // Hide the keyboard.
3602         inputMethodManager.hideSoftInputFromWindow(toolbar.windowToken, 0)
3603     }
3604
3605     // The view parameter cannot be removed because it is called from the layout onClick.
3606     fun closeTab(@Suppress("UNUSED_PARAMETER")view: View?) {
3607         // Run the command according to the number of tabs.
3608         if (tabLayout.tabCount > 1) {  // There is more than one tab open.
3609             // Get the current tab number.
3610             val currentTabNumber = tabLayout.selectedTabPosition
3611
3612             // Delete the current tab.
3613             tabLayout.removeTabAt(currentTabNumber)
3614
3615             // Delete the current page.  If the selected page number did not change during the delete (because the newly selected tab has has same number as the previously deleted tab), it will return true,
3616             // meaning that the current WebView must be reset.  Otherwise it will happen automatically as the selected tab number changes.
3617             if (webViewPagerAdapter!!.deletePage(currentTabNumber, webViewPager))
3618                 setCurrentWebView(currentTabNumber)
3619         } else {  // There is only one tab open.
3620             clearAndExit()
3621         }
3622     }
3623
3624     override fun createBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
3625         // Get the dialog.
3626         val dialog = dialogFragment.dialog!!
3627
3628         // Get the views from the dialog fragment.
3629         val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
3630         val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
3631
3632         // Extract the strings from the edit texts.
3633         val bookmarkNameString = createBookmarkNameEditText.text.toString()
3634         val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
3635
3636         // Create a favorite icon byte array output stream.
3637         val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
3638
3639         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
3640         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
3641
3642         // Convert the favorite icon byte array stream to a byte array.
3643         val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
3644
3645         // Display the new bookmark below the current items in the (0 indexed) list.
3646         val newBookmarkDisplayOrder = bookmarksListView.count
3647
3648         // Create the bookmark.
3649         bookmarksDatabaseHelper!!.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray)
3650
3651         // Update the bookmarks cursor with the current contents of this folder.
3652         bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolder)
3653
3654         // Update the list view.
3655         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
3656
3657         // Scroll to the new bookmark.
3658         bookmarksListView.setSelection(newBookmarkDisplayOrder)
3659     }
3660
3661     override fun createBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
3662         // Get the dialog.
3663         val dialog = dialogFragment.dialog!!
3664
3665         // Get handles for the views in the dialog fragment.
3666         val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
3667         val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
3668         val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
3669
3670         // Get new folder name string.
3671         val folderNameString = folderNameEditText.text.toString()
3672
3673         // Set the folder icon bitmap according to the dialog.
3674         val folderIconBitmap: Bitmap = if (defaultIconRadioButton.isChecked) {  // Use the default folder icon.
3675             // Get the default folder icon drawable.
3676             val folderIconDrawable = defaultIconImageView.drawable
3677
3678             // Convert the folder icon drawable to a bitmap drawable.
3679             val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
3680
3681             // Convert the folder icon bitmap drawable to a bitmap.
3682             folderIconBitmapDrawable.bitmap
3683         } else {  // Use the WebView favorite icon.
3684             // Copy the favorite icon bitmap to the folder icon bitmap.
3685             favoriteIconBitmap
3686         }
3687
3688         // Create a folder icon byte array output stream.
3689         val folderIconByteArrayOutputStream = ByteArrayOutputStream()
3690
3691         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
3692         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream)
3693
3694         // Convert the folder icon byte array stream to a byte array.
3695         val folderIconByteArray = folderIconByteArrayOutputStream.toByteArray()
3696
3697         // Move all the bookmarks down one in the display order.
3698         for (i in 0 until bookmarksListView.count) {
3699             // Get the bookmark database id.
3700             val databaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
3701
3702             // Move the bookmark down one slot.
3703             bookmarksDatabaseHelper!!.updateDisplayOrder(databaseId, i + 1)
3704         }
3705
3706         // Create the folder, which will be placed at the top of the list view.
3707         bookmarksDatabaseHelper!!.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray)
3708
3709         // Update the bookmarks cursor with the current contents of this folder.
3710         bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolder)
3711
3712         // Update the list view.
3713         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
3714
3715         // Scroll to the new folder.
3716         bookmarksListView.setSelection(0)
3717     }
3718
3719     private fun downloadUrlWithExternalApp(url: String) {
3720         // Create a download intent.  Not specifying the action type will display the maximum number of options.
3721         val downloadIntent = Intent()
3722
3723         // Set the URI and the mime type.
3724         downloadIntent.setDataAndType(Uri.parse(url), "text/html")
3725
3726         // Flag the intent to open in a new task.
3727         downloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
3728
3729         // Show the chooser.
3730         startActivity(Intent.createChooser(downloadIntent, getString(R.string.download_with_external_app)))
3731     }
3732
3733     private fun exitFullScreenVideo() {
3734         // Re-enable the screen timeout.
3735         fullScreenVideoFrameLayout.keepScreenOn = false
3736
3737         // Unset the full screen video flag.
3738         displayingFullScreenVideo = false
3739
3740         // Remove all the views from the full screen video frame layout.
3741         fullScreenVideoFrameLayout.removeAllViews()
3742
3743         // Hide the full screen video frame layout.
3744         fullScreenVideoFrameLayout.visibility = View.GONE
3745
3746         // Enable the sliding drawers.
3747         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
3748
3749         // Show the coordinator layout.
3750         coordinatorLayout.visibility = View.VISIBLE
3751
3752         // Apply the appropriate full screen mode flags.
3753         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
3754             // Hide the app bar if specified.
3755             if (hideAppBar) {
3756                 // Hide the tab linear layout.
3757                 tabsLinearLayout.visibility = View.GONE
3758
3759                 // Hide the app bar.
3760                 appBar.hide()
3761             }
3762
3763             /* Hide the system bars.
3764              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
3765              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
3766              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
3767              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
3768              */
3769
3770             // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
3771             @Suppress("DEPRECATION")
3772             rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
3773         } else {  // Switch to normal viewing mode.
3774             // Remove the `SYSTEM_UI` flags from the root frame layout.  The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
3775             @Suppress("DEPRECATION")
3776             rootFrameLayout.systemUiVisibility = 0
3777         }
3778     }
3779
3780     // The view parameter cannot be removed because it is called from the layout onClick.
3781     fun findNextOnPage(@Suppress("UNUSED_PARAMETER")view: View?) {
3782         // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
3783         currentWebView!!.findNext(true)
3784     }
3785
3786     // The view parameter cannot be removed because it is called from the layout onClick.
3787     fun findPreviousOnPage(@Suppress("UNUSED_PARAMETER")view: View?) {
3788         // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
3789         currentWebView!!.findNext(false)
3790     }
3791
3792     override fun finishedPopulatingBlocklists(combinedBlocklists: ArrayList<ArrayList<List<Array<String>>>>) {
3793         // Store the blocklists.
3794         easyList = combinedBlocklists[0]
3795         easyPrivacy = combinedBlocklists[1]
3796         fanboysAnnoyanceList = combinedBlocklists[2]
3797         fanboysSocialList = combinedBlocklists[3]
3798         ultraList = combinedBlocklists[4]
3799         ultraPrivacy = combinedBlocklists[5]
3800
3801         // Check to see if the activity has been restarted with a saved state.
3802         if ((savedStateArrayList == null) || (savedStateArrayList!!.size == 0)) {  // The activity has not been restarted or it was restarted on start to change the theme.
3803             // Add the first tab.
3804             addNewTab("", true)
3805         } else {  // The activity has been restarted.
3806             // Restore each tab.
3807             for (i in savedStateArrayList!!.indices) {
3808                 // Add a new tab.
3809                 tabLayout.addTab(tabLayout.newTab())
3810
3811                 // Get the new tab.
3812                 val newTab = tabLayout.getTabAt(i)!!
3813
3814                 // Set a custom view on the new tab.
3815                 newTab.setCustomView(R.layout.tab_custom_view)
3816
3817                 // Add the new page.
3818                 webViewPagerAdapter!!.restorePage(savedStateArrayList!![i], savedNestedScrollWebViewStateArrayList!![i])
3819             }
3820
3821             // Reset the saved state variables.
3822             savedStateArrayList = null
3823             savedNestedScrollWebViewStateArrayList = null
3824
3825             // Restore the selected tab position.
3826             if (savedTabPosition == 0) {  // The first tab is selected.
3827                 // Set the first page as the current WebView.
3828                 setCurrentWebView(0)
3829             } else {  // The first tab is not selected.
3830                 // Move to the selected tab.
3831                 webViewPager.currentItem = savedTabPosition
3832             }
3833
3834             // Get the intent that started the app.
3835             val intent = intent
3836
3837             // Reset the intent.  This prevents a duplicate tab from being created on restart.
3838             setIntent(Intent())
3839
3840             // Get the information from the intent.
3841             val intentAction = intent.action
3842             val intentUriData = intent.data
3843             val intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT)
3844
3845             // Determine if this is a web search.
3846             val isWebSearch = (intentAction != null) && (intentAction == Intent.ACTION_WEB_SEARCH)
3847
3848             // Only process the URI if it contains data or it is a web search.  If the user pressed the desktop icon after the app was already running the URI will be null.
3849             if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch) {
3850                 // Get the URL string.
3851                 val urlString = if (isWebSearch) {  // The intent is a web search.
3852                     // Sanitize the search input.
3853                     val encodedSearchString: String = try {
3854                         URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8")
3855                     } catch (exception: UnsupportedEncodingException) {
3856                         ""
3857                     }
3858
3859                     // Add the base search URL.
3860                     searchURL + encodedSearchString
3861                 } else { // The intent contains a URL formatted as a URI or a URL in the string extra.
3862                     // Get the URL string.
3863                     intentUriData?.toString() ?: intentStringExtra!!
3864                 }
3865
3866                 // Add a new tab if specified in the preferences.
3867                 if (sharedPreferences.getBoolean(getString(R.string.open_intents_in_new_tab_key), true)) {  // Load the URL in a new tab.
3868                     // Set the loading new intent flag.
3869                     loadingNewIntent = true
3870
3871                     // Add a new tab.
3872                     addNewTab(urlString, true)
3873                 } else {  // Load the URL in the current tab.
3874                     // Make it so.
3875                     loadUrl(currentWebView!!, urlString)
3876                 }
3877             }
3878         }
3879     }
3880
3881     // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
3882     @SuppressLint("ClickableViewAccessibility")
3883     private fun initializeApp() {
3884         // Get a handle for the input method.
3885         val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
3886
3887         // Initialize the color spans for highlighting the URLs.
3888         initialGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500))
3889         finalGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500))
3890         redColorSpan = ForegroundColorSpan(getColor(R.color.red_text))
3891
3892         // Remove the formatting from the URL edit text when the user is editing the text.
3893         urlEditText.onFocusChangeListener = View.OnFocusChangeListener { _: View?, hasFocus: Boolean ->
3894             if (hasFocus) {  // The user is editing the URL text box.
3895                 // Remove the syntax highlighting.
3896                 urlEditText.text.removeSpan(redColorSpan)
3897                 urlEditText.text.removeSpan(initialGrayColorSpan)
3898                 urlEditText.text.removeSpan(finalGrayColorSpan)
3899             } else {  // The user has stopped editing the URL text box.
3900                 // Move to the beginning of the string.
3901                 urlEditText.setSelection(0)
3902
3903                 // Reapply the syntax highlighting.
3904                 UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
3905             }
3906         }
3907
3908         // Set the go button on the keyboard to load the URL in url text box.
3909         urlEditText.setOnKeyListener { _: View?, keyCode: Int, keyEvent: KeyEvent ->
3910             // If the event is a key-down event on the `enter` button, load the URL.
3911             if ((keyEvent.action == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The enter key was pressed.
3912                 // Load the URL.
3913                 loadUrlFromTextBox()
3914
3915                 // Consume the event.
3916                 return@setOnKeyListener true
3917             } else {  // Some other key was pressed.
3918                 // Do not consume the event.
3919                 return@setOnKeyListener false
3920             }
3921         }
3922
3923         // Create an Orbot status broadcast receiver.
3924         orbotStatusBroadcastReceiver = object : BroadcastReceiver() {
3925             override fun onReceive(context: Context, intent: Intent) {
3926                 // Get the content of the status message.
3927                 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS")!!
3928
3929                 // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
3930                 if ((orbotStatus == ProxyHelper.ORBOT_STATUS_ON) && waitingForProxy) {
3931                     // Reset the waiting for proxy status.
3932                     waitingForProxy = false
3933
3934                     // Get a list of the current fragments.
3935                     val fragmentList = supportFragmentManager.fragments
3936
3937                     // Check each fragment to see if it is a waiting for proxy dialog.  Sometimes more than one is displayed.
3938                     for (i in fragmentList.indices) {
3939                         // Get the fragment tag.
3940                         val fragmentTag = fragmentList[i].tag
3941
3942                         // Check to see if it is the waiting for proxy dialog.
3943                         if (fragmentTag != null && fragmentTag == getString(R.string.waiting_for_proxy_dialog)) {
3944                             // Dismiss the waiting for proxy dialog.
3945                             (fragmentList[i] as DialogFragment).dismiss()
3946                         }
3947                     }
3948
3949                     // Reload existing URLs and load any URLs that are waiting for the proxy.
3950                     for (i in 0 until webViewPagerAdapter!!.count) {
3951                         // Get the WebView tab fragment.
3952                         val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(i)
3953
3954                         // Get the fragment view.
3955                         val fragmentView = webViewTabFragment.view
3956
3957                         // Only process the WebViews if they exist.
3958                         if (fragmentView != null) {
3959                             // Get the nested scroll WebView from the tab fragment.
3960                             val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
3961
3962                             // Get the waiting for proxy URL string.
3963                             val waitingForProxyUrlString = nestedScrollWebView.waitingForProxyUrlString
3964
3965                             // Load the pending URL if it exists.
3966                             if (waitingForProxyUrlString.isNotEmpty()) {  // A URL is waiting to be loaded.
3967                                 // Load the URL.
3968                                 loadUrl(nestedScrollWebView, waitingForProxyUrlString)
3969
3970                                 // Reset the waiting for proxy URL string.
3971                                 nestedScrollWebView.waitingForProxyUrlString = ""
3972                             } else {  // No URL is waiting to be loaded.
3973                                 // Reload the existing URL.
3974                                 nestedScrollWebView.reload()
3975                             }
3976                         }
3977                     }
3978                 }
3979             }
3980         }
3981
3982         // Register the Orbot status broadcast receiver.
3983         registerReceiver(orbotStatusBroadcastReceiver, IntentFilter("org.torproject.android.intent.action.STATUS"))
3984
3985         // Get handles for views that need to be modified.
3986         val bookmarksHeaderLinearLayout = findViewById<LinearLayout>(R.id.bookmarks_header_linearlayout)
3987         val launchBookmarksActivityFab = findViewById<FloatingActionButton>(R.id.launch_bookmarks_activity_fab)
3988         val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
3989         val createBookmarkFab = findViewById<FloatingActionButton>(R.id.create_bookmark_fab)
3990
3991         // Update the WebView pager every time a tab is modified.
3992         webViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
3993             override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
3994
3995             override fun onPageSelected(position: Int) {
3996                 // Close the find on page bar if it is open.
3997                 closeFindOnPage(null)
3998
3999                 // Set the current WebView.
4000                 setCurrentWebView(position)
4001
4002                 // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled by creating a new tab.
4003                 if (tabLayout.selectedTabPosition != position) {
4004                     // Wait until the new tab has been created.
4005                     tabLayout.post {
4006                         // Get a handle for the tab.
4007                         val tab = tabLayout.getTabAt(position)!!
4008
4009                         // Select the tab.
4010                         tab.select()
4011                     }
4012                 }
4013             }
4014
4015             override fun onPageScrollStateChanged(state: Int) {}
4016         })
4017
4018         // Handle tab selections.
4019         tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
4020             override fun onTabSelected(tab: TabLayout.Tab) {
4021                 // Select the same page in the view pager.
4022                 webViewPager.currentItem = tab.position
4023             }
4024
4025             override fun onTabUnselected(tab: TabLayout.Tab) {}
4026
4027             override fun onTabReselected(tab: TabLayout.Tab) {
4028                 // Instantiate the View SSL Certificate dialog.
4029                 val viewSslCertificateDialogFragment: DialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView!!.webViewFragmentId, currentWebView!!.getFavoriteIcon())
4030
4031                 // Display the View SSL Certificate dialog.
4032                 viewSslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.view_ssl_certificate))
4033             }
4034         })
4035
4036         // Set a touch listener on the bookmarks header linear layout so that touches don't pass through to the button underneath.
4037         bookmarksHeaderLinearLayout.setOnTouchListener { _: View?, _: MotionEvent? -> true }
4038
4039         // Set the launch bookmarks activity floating action button to launch the bookmarks activity.
4040         launchBookmarksActivityFab.setOnClickListener {
4041             // Get a copy of the favorite icon bitmap.
4042             val currentFavoriteIconBitmap = currentWebView!!.getFavoriteIcon()
4043
4044             // Create a favorite icon byte array output stream.
4045             val currentFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
4046
4047             // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
4048             currentFavoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, currentFavoriteIconByteArrayOutputStream)
4049
4050             // Convert the favorite icon byte array stream to a byte array.
4051             val currentFavoriteIconByteArray = currentFavoriteIconByteArrayOutputStream.toByteArray()
4052
4053             // Create an intent to launch the bookmarks activity.
4054             val bookmarksIntent = Intent(applicationContext, BookmarksActivity::class.java)
4055
4056             // Add the extra information to the intent.
4057             bookmarksIntent.putExtra(CURRENT_FOLDER, currentBookmarksFolder)
4058             bookmarksIntent.putExtra(CURRENT_TITLE, currentWebView!!.title)
4059             bookmarksIntent.putExtra(CURRENT_URL, currentWebView!!.url)
4060             bookmarksIntent.putExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY, currentFavoriteIconByteArray)
4061
4062             // Make it so.
4063             startActivity(bookmarksIntent)
4064         }
4065
4066         // Set the create new bookmark folder floating action button to display an alert dialog.
4067         createBookmarkFolderFab.setOnClickListener {
4068             // Create a create bookmark folder dialog.
4069             val createBookmarkFolderDialog: DialogFragment = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView!!.getFavoriteIcon())
4070
4071             // Show the create bookmark folder dialog.
4072             createBookmarkFolderDialog.show(supportFragmentManager, getString(R.string.create_folder))
4073         }
4074
4075         // Set the create new bookmark floating action button to display an alert dialog.
4076         createBookmarkFab.setOnClickListener {
4077             // Instantiate the create bookmark dialog.
4078             val createBookmarkDialog: DialogFragment = CreateBookmarkDialog.createBookmark(currentWebView!!.url!!, currentWebView!!.title!!, currentWebView!!.getFavoriteIcon())
4079
4080             // Display the create bookmark dialog.
4081             createBookmarkDialog.show(supportFragmentManager, getString(R.string.create_bookmark))
4082         }
4083
4084         // Search for the string on the page whenever a character changes in the find on page edit text.
4085         findOnPageEditText.addTextChangedListener(object : TextWatcher {
4086             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
4087
4088             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
4089
4090             override fun afterTextChanged(s: Editable) {
4091                 // Search for the text in the WebView if it is not null.  Sometimes on resume after a period of non-use the WebView will be null.
4092                 currentWebView?.findAllAsync(findOnPageEditText.text.toString())
4093             }
4094         })
4095
4096         // Set the `check mark` button for the find on page edit text keyboard to close the soft keyboard.
4097         findOnPageEditText.setOnKeyListener { _: View?, keyCode: Int, keyEvent: KeyEvent ->
4098             if ((keyEvent.action == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
4099                 // Hide the soft keyboard.
4100                 inputMethodManager.hideSoftInputFromWindow(currentWebView!!.windowToken, 0)
4101
4102                 // Consume the event.
4103                 return@setOnKeyListener true
4104             } else {  // A different key was pressed.
4105                 // Do not consume the event.
4106                 return@setOnKeyListener false
4107             }
4108         }
4109
4110         // Implement swipe to refresh.
4111         swipeRefreshLayout.setOnRefreshListener {
4112             // Reload the website.
4113             currentWebView!!.reload()
4114         }
4115
4116         // Store the default progress view offsets.
4117         defaultProgressViewStartOffset = swipeRefreshLayout.progressViewStartOffset
4118         defaultProgressViewEndOffset = swipeRefreshLayout.progressViewEndOffset
4119
4120         // Set the refresh color scheme according to the theme.
4121         swipeRefreshLayout.setColorSchemeResources(R.color.blue_text)
4122
4123         // Initialize a color background typed value.
4124         val colorBackgroundTypedValue = TypedValue()
4125
4126         // Get the color background from the theme.
4127         theme.resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true)
4128
4129         // Get the color background int from the typed value.
4130         val colorBackgroundInt = colorBackgroundTypedValue.data
4131
4132         // Set the swipe refresh background color.
4133         swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt)
4134
4135         // Set the drawer titles, which identify the drawer layouts in accessibility mode.
4136         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer))
4137         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks))
4138
4139         // Load the bookmarks folder.
4140         loadBookmarksFolder()
4141
4142         // Handle clicks on bookmarks.
4143         bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
4144             // Convert the id from long to int to match the format of the bookmarks database.
4145             val databaseId = id.toInt()
4146
4147             // Get the bookmark cursor for this ID.
4148             val bookmarkCursor = bookmarksDatabaseHelper!!.getBookmark(databaseId)
4149
4150             // Move the bookmark cursor to the first row.
4151             bookmarkCursor.moveToFirst()
4152
4153             // Act upon the bookmark according to the type.
4154             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
4155                 // Store the folder name.
4156                 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
4157
4158                 // Load the new folder.
4159                 loadBookmarksFolder()
4160             } else {  // The selected bookmark is not a folder.
4161                 // Load the bookmark URL.
4162                 loadUrl(currentWebView!!, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)))
4163
4164                 // Close the bookmarks drawer if it is not pinned.
4165                 if (!bookmarksDrawerPinned)
4166                     drawerLayout.closeDrawer(GravityCompat.END)
4167             }
4168
4169             // Close the cursor.
4170             bookmarkCursor.close()
4171         }
4172
4173         // Handle long-presses on bookmarks.
4174         bookmarksListView.onItemLongClickListener = AdapterView.OnItemLongClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
4175             // Convert the database ID from `long` to `int`.
4176             val databaseId = id.toInt()
4177
4178             // Run the commands associated with the type.
4179             if (bookmarksDatabaseHelper!!.isFolder(databaseId)) {  // The bookmark is a folder.
4180                 // Get a cursor of all the bookmarks in the folder.
4181                 val bookmarksCursor = bookmarksDatabaseHelper!!.getFolderBookmarks(databaseId)
4182
4183                 // Move to the first entry in the cursor.
4184                 bookmarksCursor.moveToFirst()
4185
4186                 // Open each bookmark
4187                 for (i in 0 until bookmarksCursor.count) {
4188                     // Load the bookmark in a new tab, moving to the tab for the first bookmark if the drawer is not pinned.
4189                     addNewTab(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), !bookmarksDrawerPinned && (i == 0))
4190
4191                     // Move to the next bookmark.
4192                     bookmarksCursor.moveToNext()
4193                 }
4194
4195                 // Close the cursor.
4196                 bookmarksCursor.close()
4197             } else {  // The bookmark is not a folder.
4198                 // Get the bookmark cursor for this ID.
4199                 val bookmarkCursor = bookmarksDatabaseHelper!!.getBookmark(databaseId)
4200
4201                 // Move the bookmark cursor to the first row.
4202                 bookmarkCursor.moveToFirst()
4203
4204                 // Load the bookmark in a new tab and move to the tab if the drawer is not pinned.
4205                 addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), !bookmarksDrawerPinned)
4206
4207                 // Close the cursor.
4208                 bookmarkCursor.close()
4209             }
4210
4211             // Close the bookmarks drawer if it is not pinned.
4212             if (!bookmarksDrawerPinned)
4213                 drawerLayout.closeDrawer(GravityCompat.END)
4214
4215             // Consume the event.
4216             true
4217         }
4218
4219         // The drawer listener is used to update the navigation menu.
4220         drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
4221             override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
4222
4223             override fun onDrawerOpened(drawerView: View) {}
4224
4225             override fun onDrawerClosed(drawerView: View) {
4226                 // Reset the drawer icon when the drawer is closed.  Otherwise, it remains an arrow if the drawer is open when the app is restarted.
4227                 actionBarDrawerToggle!!.syncState()
4228             }
4229
4230             override fun onDrawerStateChanged(newState: Int) {
4231                 if (newState == DrawerLayout.STATE_SETTLING || newState == DrawerLayout.STATE_DRAGGING) {  // A drawer is opening or closing.
4232                     // Update the navigation menu items if the WebView is not null.
4233                     if (currentWebView != null) {
4234                         navigationBackMenuItem.isEnabled = currentWebView!!.canGoBack()
4235                         navigationForwardMenuItem.isEnabled = currentWebView!!.canGoForward()
4236                         navigationHistoryMenuItem.isEnabled = currentWebView!!.canGoBack() || currentWebView!!.canGoForward()
4237                         navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + currentWebView!!.getRequestsCount(BLOCKED_REQUESTS)
4238
4239                         // Hide the keyboard (if displayed).
4240                         inputMethodManager.hideSoftInputFromWindow(currentWebView!!.windowToken, 0)
4241                     }
4242
4243                     // Clear the focus from from the URL text box.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
4244                     urlEditText.clearFocus()
4245
4246                     // Clear the focus from from the WebView if it is not null, which can happen if a user opens a drawer while the browser is being resumed.
4247                     // Clearing the focus from the WebView removes any text selection markers and context menus, which otherwise draw above the open drawers.
4248                     currentWebView?.clearFocus()
4249                 }
4250             }
4251         })
4252
4253         // Inflate a bare WebView to get the default user agent.  It is not used to render content on the screen.
4254         @SuppressLint("InflateParams") val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, null, false)
4255
4256         // Get a handle for the WebView.
4257         val bareWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
4258
4259         // Store the default user agent.
4260         webViewDefaultUserAgent = bareWebView.settings.userAgentString
4261
4262         // Destroy the bare WebView.
4263         bareWebView.destroy()
4264
4265         // Update the domains settings set.
4266         updateDomainsSettingsSet()
4267
4268         // Instantiate the check blocklist helper.
4269         checkBlocklistHelper = CheckBlocklistHelper()
4270     }
4271
4272     @SuppressLint("ClickableViewAccessibility")
4273     override fun initializeWebView(nestedScrollWebView: NestedScrollWebView, pageNumber: Int, progressBar: ProgressBar, urlString: String, restoringState: Boolean) {
4274         // Get the WebView theme.
4275         val webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value))
4276
4277         // Get the WebView theme entry values string array.
4278         val webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values)
4279
4280         // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported.
4281         if (Build.VERSION.SDK_INT >= 29 && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
4282             // Set the WebView them.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
4283             if (webViewTheme == webViewThemeEntryValuesStringArray[1]) {  // The light theme is selected.
4284                 // Turn off algorithmic darkening.
4285                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
4286
4287                 // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
4288                 // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
4289                 nestedScrollWebView.visibility = View.VISIBLE
4290             } else if (webViewTheme == webViewThemeEntryValuesStringArray[2]) {  // The dark theme is selected.
4291                 // Turn on algorithmic darkening.
4292                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
4293             } else {  // The system default theme is selected.
4294                 // Get the current theme status.
4295                 val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
4296
4297                 // Set the algorithmic darkening according to the current system theme status.
4298                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
4299                     // Turn off algorithmic darkening.
4300                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
4301
4302                     // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
4303                     // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
4304                     nestedScrollWebView.visibility = View.VISIBLE
4305                 } else {  // The system is in night mode.
4306                     // Turn on algorithmic darkening.
4307                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
4308                 }
4309             }
4310         }
4311
4312         // Get a handle for the input method manager.
4313         val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
4314
4315         // Set the app bar scrolling.
4316         nestedScrollWebView.isNestedScrollingEnabled = scrollAppBar
4317
4318         // Allow pinch to zoom.
4319         nestedScrollWebView.settings.builtInZoomControls = true
4320
4321         // Hide zoom controls.
4322         nestedScrollWebView.settings.displayZoomControls = false
4323
4324         // Don't allow mixed content (HTTP and HTTPS) on the same website.
4325         nestedScrollWebView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
4326
4327         // Set the WebView to load in overview mode (zoomed out to the maximum width).
4328         nestedScrollWebView.settings.loadWithOverviewMode = true
4329
4330         // Explicitly disable geolocation.
4331         nestedScrollWebView.settings.setGeolocationEnabled(false)
4332
4333         // Allow loading of file:// URLs.  This is necessary for opening MHT web archives, which are copied into a temporary cache location.
4334         nestedScrollWebView.settings.allowFileAccess = true
4335
4336         // Create a double-tap gesture detector to toggle full-screen mode.
4337         val doubleTapGestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
4338             // Override `onDoubleTap()`.  All other events are handled using the default settings.
4339             override fun onDoubleTap(motionEvent: MotionEvent): Boolean {
4340                 return if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
4341                     // Toggle the full screen browsing mode tracker.
4342                     inFullScreenBrowsingMode = !inFullScreenBrowsingMode
4343
4344                     // Toggle the full screen browsing mode.
4345                     if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
4346                         // Hide the app bar if specified.
4347                         if (hideAppBar) {  // App bar hiding is enabled.
4348                             // Close the find on page bar if it is visible.
4349                             closeFindOnPage(null)
4350
4351                             // Hide the tab linear layout.
4352                             tabsLinearLayout.visibility = View.GONE
4353
4354                             // Hide the app bar.
4355                             appBar.hide()
4356
4357                             // Set layout and scrolling parameters according to the position of the app bar.
4358                             if (bottomAppBar) {  // The app bar is at the bottom.
4359                                 // Reset the WebView padding to fill the available space.
4360                                 swipeRefreshLayout.setPadding(0, 0, 0, 0)
4361                             } else {  // The app bar is at the top.
4362                                 // Check to see if the app bar is normally scrolled.
4363                                 if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
4364                                     // Get the swipe refresh layout parameters.
4365                                     val swipeRefreshLayoutParams = swipeRefreshLayout.layoutParams as CoordinatorLayout.LayoutParams
4366
4367                                     // Remove the off-screen scrolling layout.
4368                                     swipeRefreshLayoutParams.behavior = null
4369                                 } else {  // The app bar is not scrolled when it is displayed.
4370                                     // Remove the padding from the top of the swipe refresh layout.
4371                                     swipeRefreshLayout.setPadding(0, 0, 0, 0)
4372
4373                                     // The swipe refresh circle must be moved above the now removed status bar location.
4374                                     swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset)
4375                                 }
4376                             }
4377                         } else {  // App bar hiding is not enabled.
4378                             // Adjust the UI for the bottom app bar.
4379                             if (bottomAppBar) {
4380                                 // Adjust the UI according to the scrolling of the app bar.
4381                                 if (scrollAppBar) {
4382                                     // Reset the WebView padding to fill the available space.
4383                                     swipeRefreshLayout.setPadding(0, 0, 0, 0)
4384                                 } else {
4385                                     // Move the WebView above the app bar layout.
4386                                     swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight)
4387                                 }
4388                             }
4389                         }
4390
4391                         /* Hide the system bars.
4392                          * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4393                          * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4394                          * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4395                          * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4396                          */
4397
4398                         // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
4399                         @Suppress("DEPRECATION")
4400                         rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
4401                                 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
4402                     } else {  // Switch to normal viewing mode.
4403                         // Show the app bar if it was hidden.
4404                         if (hideAppBar) {
4405                             // Show the tab linear layout.
4406                             tabsLinearLayout.visibility = View.VISIBLE
4407
4408                             // Show the app bar.
4409                             appBar.show()
4410                         }
4411
4412                         // Set layout and scrolling parameters according to the position of the app bar.
4413                         if (bottomAppBar) {  // The app bar is at the bottom.
4414                             // Adjust the UI.
4415                             if (scrollAppBar) {
4416                                 // Reset the WebView padding to fill the available space.
4417                                 swipeRefreshLayout.setPadding(0, 0, 0, 0)
4418                             } else {
4419                                 // Move the WebView above the app bar layout.
4420                                 swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight)
4421                             }
4422                         } else {  // The app bar is at the top.
4423                             // Check to see if the app bar is normally scrolled.
4424                             if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
4425                                 // Get the swipe refresh layout parameters.
4426                                 val swipeRefreshLayoutParams = swipeRefreshLayout.layoutParams as CoordinatorLayout.LayoutParams
4427
4428                                 // Add the off-screen scrolling layout.
4429                                 swipeRefreshLayoutParams.behavior = AppBarLayout.ScrollingViewBehavior()
4430                             } else {  // The app bar is not scrolled when it is displayed.
4431                                 // The swipe refresh layout must be manually moved below the app bar layout.
4432                                 swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0)
4433
4434                                 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
4435                                 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight)
4436                             }
4437                         }
4438
4439                         // Remove the `SYSTEM_UI` flags from the root frame layout.  The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
4440                         @Suppress("DEPRECATION")
4441                         rootFrameLayout.systemUiVisibility = 0
4442                     }
4443
4444                     // Consume the double-tap.
4445                     true
4446                 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
4447                     // Return false.
4448                     false
4449                 }
4450             }
4451
4452             override fun onFling(motionEvent1: MotionEvent, motionEvent2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
4453                 // Scroll the bottom app bar if enabled.
4454                 if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning) {
4455                     // Calculate the Y change.
4456                     val motionY = motionEvent2.y - motionEvent1.y
4457
4458                     // Scroll the app bar if the change is greater than 50 pixels.
4459                     if (motionY > 50) {
4460                         // Animate the bottom app bar onto the screen.
4461                         objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0f)
4462                     } else if (motionY < -50) {
4463                         // Animate the bottom app bar off the screen.
4464                         objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.height.toFloat())
4465                     }
4466
4467                     // Make it so.
4468                     objectAnimator.start()
4469                 }
4470
4471                 // Do not consume the event.
4472                 return false
4473             }
4474         })
4475
4476         // Pass all touch events on the WebView through the double-tap gesture detector.
4477         nestedScrollWebView.setOnTouchListener { view: View, motionEvent: MotionEvent? ->
4478             // Call `performClick()` on the view, which is required for accessibility.
4479             view.performClick()
4480
4481             // Check for double-taps.
4482             doubleTapGestureDetector.onTouchEvent(motionEvent!!)
4483         }
4484
4485         // Register the WebView for a context menu.  This is used to see link targets and download images.
4486         registerForContextMenu(nestedScrollWebView)
4487
4488         // Allow the downloading of files.
4489         nestedScrollWebView.setDownloadListener { downloadUrlString: String?, userAgent: String?, contentDisposition: String?, mimetype: String?, contentLength: Long ->
4490             // Check the download preference.
4491             if (downloadWithExternalApp) {  // Download with an external app.
4492                 downloadUrlWithExternalApp(downloadUrlString!!)
4493             } else {  // Handle the download inside of Privacy Browser.
4494                 // Define a formatted file size string.
4495
4496                 // Process the content length if it contains data.
4497                 val formattedFileSizeString = if (contentLength > 0) {  // The content length is greater than 0.
4498                     // Format the content length as a string.
4499                     NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes)
4500                 } else {  // The content length is not greater than 0.
4501                     // Set the formatted file size string to be `unknown size`.
4502                     getString(R.string.unknown_size)
4503                 }
4504
4505                 // Get the file name from the content disposition.
4506                 val fileNameString = UrlHelper.getFileName(this, contentDisposition, mimetype, downloadUrlString!!)
4507
4508                 // Instantiate the save dialog.
4509                 val saveDialogFragment = SaveDialog.saveUrl(downloadUrlString, fileNameString, formattedFileSizeString, userAgent!!, nestedScrollWebView.acceptCookies)
4510
4511                 // Try to show the dialog.  The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
4512                 try {
4513                     // Show the save dialog.
4514                     saveDialogFragment.show(supportFragmentManager, getString(R.string.save_dialog))
4515                 } catch (exception: Exception) {  // The dialog could not be shown.
4516                     // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
4517                     pendingDialogsArrayList.add(PendingDialogDataClass(saveDialogFragment, getString(R.string.save_dialog)))
4518                 }
4519             }
4520         }
4521
4522         // Update the find on page count.
4523         nestedScrollWebView.setFindListener { activeMatchOrdinal, numberOfMatches, isDoneCounting ->
4524             if (isDoneCounting && (numberOfMatches == 0)) {  // There are no matches.
4525                 // Set the find on page count text view to be `0/0`.
4526                 findOnPageCountTextView.setText(R.string.zero_of_zero)
4527             } else if (isDoneCounting) {  // There are matches.
4528                 // The active match ordinal is zero-based.
4529                 val activeMatch = activeMatchOrdinal + 1
4530
4531                 // Build the match string.
4532                 val matchString = "$activeMatch/$numberOfMatches"
4533
4534                 // Update the find on page count text view.
4535                 findOnPageCountTextView.text = matchString
4536             }
4537         }
4538
4539         // Process scroll changes.
4540         nestedScrollWebView.setOnScrollChangeListener { _: View?, _: Int, _: Int, _: Int, _: Int ->
4541             // Set the swipe to refresh status.
4542             if (nestedScrollWebView.swipeToRefresh)  // Only enable swipe to refresh if the WebView is scrolled to the top.
4543                 swipeRefreshLayout.isEnabled = nestedScrollWebView.scrollY == 0
4544             else  // Disable swipe to refresh.
4545                 swipeRefreshLayout.isEnabled = false
4546
4547             // Reinforce the system UI visibility flags if in full screen browsing mode.
4548             // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
4549             if (inFullScreenBrowsingMode) {
4550                 /* Hide the system bars.
4551                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4552                  * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4553                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4554                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4555                  */
4556
4557                 // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
4558                 @Suppress("DEPRECATION")
4559                 rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
4560             }
4561         }
4562
4563         // Set the web chrome client.
4564         nestedScrollWebView.webChromeClient = object : WebChromeClient() {
4565             // Update the progress bar when a page is loading.
4566             override fun onProgressChanged(view: WebView, progress: Int) {
4567                 // Update the progress bar.
4568                 progressBar.progress = progress
4569
4570                 // Set the visibility of the progress bar.
4571                 if (progress < 100) {
4572                     // Show the progress bar.
4573                     progressBar.visibility = View.VISIBLE
4574                 } else {
4575                     // Hide the progress bar.
4576                     progressBar.visibility = View.GONE
4577
4578                     //Stop the swipe to refresh indicator if it is running
4579                     swipeRefreshLayout.isRefreshing = false
4580
4581                     // Make the current WebView visible.  If this is a new tab, the current WebView would have been created invisible in `webview_framelayout` to prevent a white background splash in night mode.
4582                     nestedScrollWebView.visibility = View.VISIBLE
4583                 }
4584             }
4585
4586             // Set the favorite icon when it changes.
4587             override fun onReceivedIcon(view: WebView, icon: Bitmap) {
4588                 // Only update the favorite icon if the website has finished loading and the new favorite icon height is greater than the current favorite icon height.
4589                 // This prevents low resolution icons from replacing high resolution one.
4590                 // The check for the visibility of the progress bar can possibly be removed once https://redmine.stoutner.com/issues/747 is fixed.
4591                 if ((progressBar.visibility == View.GONE) && (icon.height > nestedScrollWebView.getFavoriteIconHeight())) {
4592                     // Store the new favorite icon.
4593                     nestedScrollWebView.setFavoriteIcon(icon)
4594
4595                     // Get the current page position.
4596                     val currentPosition = webViewPagerAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
4597
4598                     // Get the current tab.
4599                     val tab = tabLayout.getTabAt(currentPosition)
4600
4601                     // Check to see if the tab has been populated.
4602                     if (tab != null) {
4603                         // Get the custom view from the tab.
4604                         val tabView = tab.customView
4605
4606                         // Check to see if the custom tab view has been populated.
4607                         if (tabView != null) {
4608                             // Get the favorite icon image view from the tab.
4609                             val tabFavoriteIconImageView = tabView.findViewById<ImageView>(R.id.favorite_icon_imageview)
4610
4611                             // Display the favorite icon in the tab.
4612                             tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true))
4613                         }
4614                     }
4615                 }
4616             }
4617
4618             // Save a copy of the title when it changes.
4619             override fun onReceivedTitle(view: WebView, title: String) {
4620                 // Get the current page position.
4621                 val currentPosition = webViewPagerAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
4622
4623                 // Get the current tab.
4624                 val tab = tabLayout.getTabAt(currentPosition)
4625
4626                 // Only populate the title text view if the tab has been fully created.
4627                 if (tab != null) {
4628                     // Get the custom view from the tab.
4629                     val tabView = tab.customView
4630
4631                     // Only populate the title text view if the tab view has been fully populated.
4632                     if (tabView != null) {
4633                         // Get the title text view from the tab.
4634                         val tabTitleTextView = tabView.findViewById<TextView>(R.id.title_textview)
4635
4636                         // Set the title according to the URL.
4637                         if (title == "about:blank") {
4638                             // Set the title to indicate a new tab.
4639                             tabTitleTextView.setText(R.string.new_tab)
4640                         } else {
4641                             // Set the title as the tab text.
4642                             tabTitleTextView.text = title
4643                         }
4644                     }
4645                 }
4646             }
4647
4648             // Enter full screen video.
4649             override fun onShowCustomView(video: View, callback: CustomViewCallback) {
4650                 // Set the full screen video flag.
4651                 displayingFullScreenVideo = true
4652
4653                 // Hide the keyboard.
4654                 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.windowToken, 0)
4655
4656                 // Hide the coordinator layout.
4657                 coordinatorLayout.visibility = View.GONE
4658
4659                 /* Hide the system bars.
4660                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4661                  * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4662                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4663                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4664                  */
4665
4666                 // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
4667                 @Suppress("DEPRECATION")
4668                 rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
4669
4670                 // Disable the sliding drawers.
4671                 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
4672
4673                 // Add the video view to the full screen video frame layout.
4674                 fullScreenVideoFrameLayout.addView(video)
4675
4676                 // Show the full screen video frame layout.
4677                 fullScreenVideoFrameLayout.visibility = View.VISIBLE
4678
4679                 // Disable the screen timeout while the video is playing.  YouTube does this automatically, but not all other videos do.
4680                 fullScreenVideoFrameLayout.keepScreenOn = true
4681             }
4682
4683             // Exit full screen video.
4684             override fun onHideCustomView() {
4685                 // Exit the full screen video.
4686                 exitFullScreenVideo()
4687             }
4688
4689             // Upload files.
4690             override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
4691                 // Store the file path callback.
4692                 fileChooserCallback = filePathCallback
4693
4694                 // Create an intent to open a chooser based on the file chooser parameters.
4695                 val fileChooserIntent = fileChooserParams.createIntent()
4696
4697                 // Check to see if the file chooser intent resolves to an installed package.
4698                 if (fileChooserIntent.resolveActivity(packageManager) != null) {  // The file chooser intent is fine.
4699                     // Launch the file chooser intent.
4700                     browseFileUploadActivityResultLauncher.launch(fileChooserIntent)
4701                 } else {  // The file chooser intent will cause a crash.
4702                     // Create a generic intent to open a chooser.
4703                     val genericFileChooserIntent = Intent(Intent.ACTION_GET_CONTENT)
4704
4705                     // Request an openable file.
4706                     genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE)
4707
4708                     // Set the file type to everything.
4709                     genericFileChooserIntent.type = "*/*"
4710
4711                     // Launch the generic file chooser intent.
4712                     browseFileUploadActivityResultLauncher.launch(genericFileChooserIntent)
4713                 }
4714
4715                 // Handle the event.
4716                 return true
4717             }
4718         }
4719         nestedScrollWebView.webViewClient = object : WebViewClient() {
4720             // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
4721             override fun shouldOverrideUrlLoading(view: WebView, webResourceRequest: WebResourceRequest): Boolean {
4722                 // Get the URL from the web resource request.
4723                 var requestUrlString = webResourceRequest.url.toString()
4724
4725                 // Sanitize the url.
4726                 requestUrlString = sanitizeUrl(requestUrlString)
4727
4728                 // Handle the URL according to the type.
4729                 return if (requestUrlString.startsWith("http")) {  // Load the URL in Privacy Browser.
4730                     // Load the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
4731                     loadUrl(nestedScrollWebView, requestUrlString)
4732
4733                     // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
4734                     // Custom headers cannot be added if false is returned and the WebView handles the loading of the URL.
4735                     true
4736                 } else if (requestUrlString.startsWith("mailto:")) {  // Load the email address in an external email program.
4737                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
4738                     val emailIntent = Intent(Intent.ACTION_SENDTO)
4739
4740                     // Parse the url and set it as the data for the intent.
4741                     emailIntent.data = Uri.parse(requestUrlString)
4742
4743                     // Open the email program in a new task instead of as part of Privacy Browser.
4744                     emailIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
4745
4746                     try {
4747                         // Make it so.
4748                         startActivity(emailIntent)
4749                     } catch (exception: ActivityNotFoundException) {
4750                         // Display a snackbar.
4751                         Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
4752                     }
4753
4754                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
4755                     true
4756                 } else if (requestUrlString.startsWith("tel:")) {  // Load the phone number in the dialer.
4757                     // Create a dial intent.
4758                     val dialIntent = Intent(Intent.ACTION_DIAL)
4759
4760                     // Add the phone number to the intent.
4761                     dialIntent.data = Uri.parse(requestUrlString)
4762
4763                     // Open the dialer in a new task instead of as part of Privacy Browser.
4764                     dialIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
4765
4766                     try {
4767                         // Make it so.
4768                         startActivity(dialIntent)
4769                     } catch (exception: ActivityNotFoundException) {
4770                         // Display a snackbar.
4771                         Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
4772                     }
4773
4774                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
4775                     true
4776                 } else {  // Load a system chooser to select an app that can handle the URL.
4777                     // Create a generic intent to open an app.
4778                     val genericIntent = Intent(Intent.ACTION_VIEW)
4779
4780                     // Add the URL to the intent.
4781                     genericIntent.data = Uri.parse(requestUrlString)
4782
4783                     // List all apps that can handle the URL instead of just opening the first one.
4784                     genericIntent.addCategory(Intent.CATEGORY_BROWSABLE)
4785
4786                     // Open the app in a new task instead of as part of Privacy Browser.
4787                     genericIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
4788
4789                     try {
4790                         // Make it so.
4791                         startActivity(genericIntent)
4792                     } catch (exception: ActivityNotFoundException) {
4793                         // Display a snackbar.
4794                         Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url, requestUrlString), Snackbar.LENGTH_SHORT).show()
4795                     }
4796
4797                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
4798                     true
4799                 }
4800             }
4801
4802             // Check requests against the block lists.
4803             override fun shouldInterceptRequest(view: WebView, webResourceRequest: WebResourceRequest): WebResourceResponse? {
4804                 // Get the URL.
4805                 val requestUrlString = webResourceRequest.url.toString()
4806
4807                 // Check to see if the resource request is for the main URL.
4808                 if (requestUrlString == nestedScrollWebView.currentUrl) {
4809                     // `return null` loads the resource request, which should never be blocked if it is the main URL.
4810                     return null
4811                 }
4812
4813                 // Wait until the blocklists have been populated.  When Privacy Browser is being resumed after having the process killed in the background it will try to load the URLs immediately.
4814                 while (ultraPrivacy == null) {
4815                     try {
4816                         // Check to see if the blocklists have been populated after 100 ms.
4817                         Thread.sleep(100)
4818                     } catch (exception: InterruptedException) {
4819                         // Do nothing.
4820                     }
4821                 }
4822
4823                 // Create an empty web resource response to be used if the resource request is blocked.
4824                 val emptyWebResourceResponse = WebResourceResponse("text/plain", "utf8", ByteArrayInputStream("".toByteArray()))
4825
4826                 // Initialize the variables.
4827                 var whitelistResultStringArray: Array<String>? = null
4828                 var isThirdPartyRequest = false
4829
4830                 // Get the current URL.  `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
4831                 var currentBaseDomain = nestedScrollWebView.currentDomainName
4832
4833                 // Store a copy of the current domain for use in later requests.
4834                 val currentDomain = currentBaseDomain
4835
4836                 // Get the request host name.
4837                 var requestBaseDomain = webResourceRequest.url.host
4838
4839                 // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
4840                 if (currentBaseDomain.isNotEmpty() && (requestBaseDomain != null)) {
4841                     // Determine the current base domain.
4842                     while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
4843                         // Remove the first subdomain.
4844                         currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1)
4845                     }
4846
4847                     // Determine the request base domain.
4848                     while (requestBaseDomain!!.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
4849                         // Remove the first subdomain.
4850                         requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1)
4851                     }
4852
4853                     // Update the third party request tracker.
4854                     isThirdPartyRequest = currentBaseDomain != requestBaseDomain
4855                 }
4856
4857                 // Get the current WebView page position.
4858                 val webViewPagePosition = webViewPagerAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
4859
4860                 // Determine if the WebView is currently displayed.
4861                 val webViewDisplayed = webViewPagePosition == tabLayout.selectedTabPosition
4862
4863                 // Block third-party requests if enabled.
4864                 if (isThirdPartyRequest && nestedScrollWebView.blockAllThirdPartyRequests) {
4865                     // Add the result to the resource requests.
4866                     nestedScrollWebView.addResourceRequest(arrayOf(REQUEST_THIRD_PARTY, requestUrlString))
4867
4868                     // Increment the blocked requests counters.
4869                     nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
4870                     nestedScrollWebView.incrementRequestsCount(THIRD_PARTY_REQUESTS)
4871
4872                     // Update the titles of the blocklist menu items if the WebView is currently displayed.
4873                     if (webViewDisplayed) {
4874                         // Updating the UI must be run from the UI thread.
4875                         runOnUiThread {
4876                             // Update the menu item titles.
4877                             navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
4878
4879                             // Update the options menu if it has been populated.
4880                             if (optionsMenu != null) {
4881                                 optionsBlocklistsMenuItem.title = getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
4882                                 optionsBlockAllThirdPartyRequestsMenuItem.title =
4883                                     nestedScrollWebView.getRequestsCount(THIRD_PARTY_REQUESTS).toString() + " - " + getString(R.string.block_all_third_party_requests)
4884                             }
4885                         }
4886                     }
4887
4888                     // The resource request was blocked.  Return an empty web resource response.
4889                     return emptyWebResourceResponse
4890                 }
4891
4892                 // Check UltraList if it is enabled.
4893                 if (nestedScrollWebView.ultraListEnabled) {
4894                     // Check the URL against UltraList.
4895                     val ultraListResults = checkBlocklistHelper.checkBlocklist(currentDomain, requestUrlString, isThirdPartyRequest, ultraList)
4896
4897                     // Process the UltraList results.
4898                     if (ultraListResults[0] == REQUEST_BLOCKED) {  // The resource request matched UltraList's blacklist.
4899                         // Add the result to the resource requests.
4900                         nestedScrollWebView.addResourceRequest(arrayOf(ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]))
4901
4902                         // Increment the blocked requests counters.
4903                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
4904                         nestedScrollWebView.incrementRequestsCount(ULTRALIST)
4905
4906                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
4907                         if (webViewDisplayed) {
4908                             // Updating the UI must be run from the UI thread.
4909                             runOnUiThread {
4910                                 // Update the menu item titles.
4911                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
4912
4913                                 // Update the options menu if it has been populated.
4914                                 if (optionsMenu != null) {
4915                                     optionsBlocklistsMenuItem.title = getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
4916                                     optionsUltraListMenuItem.title = nestedScrollWebView.getRequestsCount(ULTRALIST).toString() + " - " + getString(R.string.ultralist)
4917                                 }
4918                             }
4919                         }
4920
4921                         // The resource request was blocked.  Return an empty web resource response.
4922                         return emptyWebResourceResponse
4923                     } else if (ultraListResults[0] == REQUEST_ALLOWED) {  // The resource request matched UltraList's whitelist.
4924                         // Add a whitelist entry to the resource requests array.
4925                         nestedScrollWebView.addResourceRequest(arrayOf(ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]))
4926
4927                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
4928                         return null
4929                     }
4930                 }
4931
4932                 // Check UltraPrivacy if it is enabled.
4933                 if (nestedScrollWebView.ultraPrivacyEnabled) {
4934                     // Check the URL against UltraPrivacy.
4935                     val ultraPrivacyResults = checkBlocklistHelper.checkBlocklist(currentDomain, requestUrlString, isThirdPartyRequest, ultraPrivacy!!)
4936
4937                     // Process the UltraPrivacy results.
4938                     if (ultraPrivacyResults[0] == REQUEST_BLOCKED) {  // The resource request matched UltraPrivacy's blacklist.
4939                         // Add the result to the resource requests.
4940                         nestedScrollWebView.addResourceRequest(arrayOf(ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
4941                             ultraPrivacyResults[5]))
4942
4943                         // Increment the blocked requests counters.
4944                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
4945                         nestedScrollWebView.incrementRequestsCount(ULTRAPRIVACY)
4946
4947                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
4948                         if (webViewDisplayed) {
4949                             // Updating the UI must be run from the UI thread.
4950                             runOnUiThread {
4951                                 // Update the menu item titles.
4952                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
4953
4954                                 // Update the options menu if it has been populated.
4955                                 if (optionsMenu != null) {
4956                                     optionsBlocklistsMenuItem.title = getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
4957                                     optionsUltraPrivacyMenuItem.title = nestedScrollWebView.getRequestsCount(ULTRAPRIVACY).toString() + " - " + getString(R.string.ultraprivacy)
4958                                 }
4959                             }
4960                         }
4961
4962                         // The resource request was blocked.  Return an empty web resource response.
4963                         return emptyWebResourceResponse
4964                     } else if (ultraPrivacyResults[0] == REQUEST_ALLOWED) {  // The resource request matched UltraPrivacy's whitelist.
4965                         // Add a whitelist entry to the resource requests array.
4966                         nestedScrollWebView.addResourceRequest(arrayOf(ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
4967                             ultraPrivacyResults[5]))
4968
4969                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
4970                         return null
4971                     }
4972                 }
4973
4974                 // Check EasyList if it is enabled.
4975                 if (nestedScrollWebView.easyListEnabled) {
4976                     // Check the URL against EasyList.
4977                     val easyListResults = checkBlocklistHelper.checkBlocklist(currentDomain, requestUrlString, isThirdPartyRequest, easyList)
4978
4979                     // Process the EasyList results.
4980                     if (easyListResults[0] == REQUEST_BLOCKED) {  // The resource request matched EasyList's blacklist.
4981                         // Add the result to the resource requests.
4982                         nestedScrollWebView.addResourceRequest(arrayOf(easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]))
4983
4984                         // Increment the blocked requests counters.
4985                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
4986                         nestedScrollWebView.incrementRequestsCount(EASYLIST)
4987
4988                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
4989                         if (webViewDisplayed) {
4990                             // Updating the UI must be run from the UI thread.
4991                             runOnUiThread {
4992                                 // Update the menu item titles.
4993                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
4994
4995                                 // Update the options menu if it has been populated.
4996                                 if (optionsMenu != null) {
4997                                     optionsBlocklistsMenuItem.title = getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
4998                                     optionsEasyListMenuItem.title = nestedScrollWebView.getRequestsCount(EASYLIST).toString() + " - " + getString(R.string.easylist)
4999                                 }
5000                             }
5001                         }
5002
5003                         // The resource request was blocked.  Return an empty web resource response.
5004                         return emptyWebResourceResponse
5005                     } else if (easyListResults[0] == REQUEST_ALLOWED) {  // The resource request matched EasyList's whitelist.
5006                         // Update the whitelist result string array tracker.
5007                         whitelistResultStringArray = arrayOf(easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5])
5008                     }
5009                 }
5010
5011                 // Check EasyPrivacy if it is enabled.
5012                 if (nestedScrollWebView.easyPrivacyEnabled) {
5013                     // Check the URL against EasyPrivacy.
5014                     val easyPrivacyResults = checkBlocklistHelper.checkBlocklist(currentDomain, requestUrlString, isThirdPartyRequest, easyPrivacy)
5015
5016                     // Process the EasyPrivacy results.
5017                     if (easyPrivacyResults[0] == REQUEST_BLOCKED) {  // The resource request matched EasyPrivacy's blacklist.
5018                         // Add the result to the resource requests.
5019                         nestedScrollWebView.addResourceRequest(arrayOf(easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]))
5020
5021                         // Increment the blocked requests counters.
5022                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5023                         nestedScrollWebView.incrementRequestsCount(EASYPRIVACY)
5024
5025                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5026                         if (webViewDisplayed) {
5027                             // Updating the UI must be run from the UI thread.
5028                             runOnUiThread {
5029                                 // Update the menu item titles.
5030                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5031
5032                                 // Update the options menu if it has been populated.
5033                                 if (optionsMenu != null) {
5034                                     optionsBlocklistsMenuItem.title = getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5035                                     optionsEasyPrivacyMenuItem.title = nestedScrollWebView.getRequestsCount(EASYPRIVACY).toString() + " - " + getString(R.string.easyprivacy)
5036                                 }
5037                             }
5038                         }
5039
5040                         // The resource request was blocked.  Return an empty web resource response.
5041                         return emptyWebResourceResponse
5042                     } else if (easyPrivacyResults[0] == REQUEST_ALLOWED) {  // The resource request matched EasyPrivacy's whitelist.
5043                         // Update the whitelist result string array tracker.
5044                         whitelistResultStringArray = arrayOf(easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5])
5045                     }
5046                 }
5047
5048                 // Check Fanboy’s Annoyance List if it is enabled.
5049                 if (nestedScrollWebView.fanboysAnnoyanceListEnabled) {
5050                     // Check the URL against Fanboy's Annoyance List.
5051                     val fanboysAnnoyanceListResults = checkBlocklistHelper.checkBlocklist(currentDomain, requestUrlString, isThirdPartyRequest, fanboysAnnoyanceList)
5052
5053                     // Process the Fanboy's Annoyance List results.
5054                     if (fanboysAnnoyanceListResults[0] == REQUEST_BLOCKED) {  // The resource request matched Fanboy's Annoyance List's blacklist.
5055                         // Add the result to the resource requests.
5056                         nestedScrollWebView.addResourceRequest(arrayOf(fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5057                             fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]))
5058
5059                         // Increment the blocked requests counters.
5060                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5061                         nestedScrollWebView.incrementRequestsCount(FANBOYS_ANNOYANCE_LIST)
5062
5063                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5064                         if (webViewDisplayed) {
5065                             // Updating the UI must be run from the UI thread.
5066                             runOnUiThread {
5067                                 // Update the menu item titles.
5068                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5069
5070                                 // Update the options menu if it has been populated.
5071                                 if (optionsMenu != null) {
5072                                     optionsBlocklistsMenuItem.title = getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5073                                     optionsFanboysAnnoyanceListMenuItem.title = nestedScrollWebView.getRequestsCount(FANBOYS_ANNOYANCE_LIST).toString() + " - " + getString(R.string.fanboys_annoyance_list)
5074                                 }
5075                             }
5076                         }
5077
5078                         // The resource request was blocked.  Return an empty web resource response.
5079                         return emptyWebResourceResponse
5080                     } else if (fanboysAnnoyanceListResults[0] == REQUEST_ALLOWED) {  // The resource request matched Fanboy's Annoyance List's whitelist.
5081                         // Update the whitelist result string array tracker.
5082                         whitelistResultStringArray = arrayOf(fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5083                             fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5])
5084                     }
5085                 } else if (nestedScrollWebView.fanboysSocialBlockingListEnabled) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
5086                     // Check the URL against Fanboy's Annoyance List.
5087                     val fanboysSocialListResults = checkBlocklistHelper.checkBlocklist(currentDomain, requestUrlString, isThirdPartyRequest, fanboysSocialList)
5088
5089                     // Process the Fanboy's Social Blocking List results.
5090                     if (fanboysSocialListResults[0] == REQUEST_BLOCKED) {  // The resource request matched Fanboy's Social Blocking List's blacklist.
5091                         // Add the result to the resource requests.
5092                         nestedScrollWebView.addResourceRequest(arrayOf(fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5093                             fanboysSocialListResults[4], fanboysSocialListResults[5]))
5094
5095                         // Increment the blocked requests counters.
5096                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5097                         nestedScrollWebView.incrementRequestsCount(FANBOYS_SOCIAL_BLOCKING_LIST)
5098
5099                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5100                         if (webViewDisplayed) {
5101                             // Updating the UI must be run from the UI thread.
5102                             runOnUiThread {
5103                                 // Update the menu item titles.
5104                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5105
5106                                 // Update the options menu if it has been populated.
5107                                 if (optionsMenu != null) {
5108                                     optionsBlocklistsMenuItem.title = getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5109                                     optionsFanboysSocialBlockingListMenuItem.title =
5110                                         nestedScrollWebView.getRequestsCount(FANBOYS_SOCIAL_BLOCKING_LIST).toString() + " - " + getString(R.string.fanboys_social_blocking_list)
5111                                 }
5112                             }
5113                         }
5114
5115                         // The resource request was blocked.  Return an empty web resource response.
5116                         return emptyWebResourceResponse
5117                     } else if (fanboysSocialListResults[0] == REQUEST_ALLOWED) {  // The resource request matched Fanboy's Social Blocking List's whitelist.
5118                         // Update the whitelist result string array tracker.
5119                         whitelistResultStringArray = arrayOf(fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3], fanboysSocialListResults[4],
5120                             fanboysSocialListResults[5])
5121                     }
5122                 }
5123
5124                 // Add the request to the log because it hasn't been processed by any of the previous checks.
5125                 if (whitelistResultStringArray != null) {  // The request was processed by a whitelist.
5126                     nestedScrollWebView.addResourceRequest(whitelistResultStringArray)
5127                 } else {  // The request didn't match any blocklist entry.  Log it as a default request.
5128                     nestedScrollWebView.addResourceRequest(arrayOf(REQUEST_DEFAULT, requestUrlString))
5129                 }
5130
5131                 // The resource request has not been blocked.  `return null` loads the requested resource.
5132                 return null
5133             }
5134
5135             // Handle HTTP authentication requests.
5136             override fun onReceivedHttpAuthRequest(view: WebView, handler: HttpAuthHandler, host: String, realm: String) {
5137                 // Store the handler.
5138                 nestedScrollWebView.httpAuthHandler = handler
5139
5140                 // Instantiate an HTTP authentication dialog.
5141                 val httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.webViewFragmentId)
5142
5143                 // Show the HTTP authentication dialog.
5144                 httpAuthenticationDialogFragment.show(supportFragmentManager, getString(R.string.http_authentication))
5145             }
5146
5147             override fun onPageStarted(webView: WebView, url: String, favicon: Bitmap?) {
5148                 // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
5149                 // This should only be populated if it is greater than 0 because otherwise it will be reset to 0 if the app bar is hidden in full screen browsing mode.
5150                 if (appBarLayout.height > 0)
5151                     appBarHeight = appBarLayout.height
5152
5153                 // Set the padding and layout settings according to the position of the app bar.
5154                 if (bottomAppBar) {  // The app bar is on the bottom.
5155                     // Adjust the UI.
5156                     if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
5157                         // Reset the WebView padding to fill the available space.
5158                         swipeRefreshLayout.setPadding(0, 0, 0, 0)
5159                     } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
5160                         // Move the WebView above the app bar layout.
5161                         swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight)
5162                     }
5163                 } else {  // The app bar is on the top.
5164                     // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.  This can't be done in `appAppSettings()` because the app bar is not yet populated there.
5165                     if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
5166                         // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
5167                         swipeRefreshLayout.setPadding(0, 0, 0, 0)
5168
5169                         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5170                         swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset)
5171                     } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
5172                         // The swipe refresh layout must be manually moved below the app bar layout.
5173                         swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0)
5174
5175                         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5176                         swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight)
5177                     }
5178                 }
5179
5180                 // Reset the list of resource requests.
5181                 nestedScrollWebView.clearResourceRequests()
5182
5183                 // Reset the requests counters.
5184                 nestedScrollWebView.resetRequestsCounters()
5185
5186                 // Get the current page position.
5187                 val currentPagePosition = webViewPagerAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
5188
5189                 // Update the URL text bar if the page is currently selected and the URL edit text is not currently being edited.
5190                 if ((tabLayout.selectedTabPosition == currentPagePosition) && !urlEditText.hasFocus()) {
5191                     // Display the formatted URL text.
5192                     urlEditText.setText(url)
5193
5194                     // Highlight the URL syntax.
5195                     UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
5196
5197                     // Hide the keyboard.
5198                     inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.windowToken, 0)
5199                 }
5200
5201                 // Reset the list of host IP addresses.
5202                 nestedScrollWebView.currentIpAddresses = ""
5203
5204                 // Get a URI for the current URL.
5205                 val currentUri = Uri.parse(url)
5206
5207                 // Get the current domain name.
5208                 val currentDomainName = currentUri.host
5209
5210                 // Get the IP addresses for the current domain.
5211                 if ((currentDomainName != null) && currentDomainName.isNotEmpty())
5212                     GetHostIpAddressesCoroutine.checkPinnedMismatch(currentDomainName, nestedScrollWebView, supportFragmentManager, getString(R.string.pinned_mismatch))
5213
5214                 // Replace Refresh with Stop if the options menu has been created and the WebView is currently displayed.  (The first WebView typically begins loading before the menu items are instantiated.)
5215                 if ((optionsMenu != null) && (webView == currentWebView)) {
5216                     // Set the title.
5217                     optionsRefreshMenuItem.setTitle(R.string.stop)
5218
5219                     // Set the icon if it is displayed in the AppBar.
5220                     if (displayAdditionalAppBarIcons)
5221                         optionsRefreshMenuItem.setIcon(R.drawable.close_blue)
5222                 }
5223             }
5224
5225             override fun onPageFinished(webView: WebView, url: String) {
5226                 // Flush any cookies to persistent storage.  The cookie manager has become very lazy about flushing cookies in recent versions.
5227                 if (nestedScrollWebView.acceptCookies)
5228                     cookieManager.flush()
5229
5230                 // Update the Refresh menu item if the options menu has been created and the WebView is currently displayed.
5231                 if (optionsMenu != null && (webView == currentWebView)) {
5232                     // Reset the Refresh title.
5233                     optionsRefreshMenuItem.setTitle(R.string.refresh)
5234
5235                     // Reset the icon if it is displayed in the app bar.
5236                     if (displayAdditionalAppBarIcons)
5237                         optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled)
5238                 }
5239
5240                 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
5241                 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
5242                 val privateDataDirectoryString = applicationInfo.dataDir
5243
5244                 // Clear the cache, history, and logcat if Incognito Mode is enabled.
5245                 if (incognitoModeEnabled) {
5246                     // Clear the cache.  `true` includes disk files.
5247                     nestedScrollWebView.clearCache(true)
5248
5249                     // Clear the back/forward history.
5250                     nestedScrollWebView.clearHistory()
5251
5252                     // Manually delete cache folders.
5253                     try {
5254                         // Delete the main cache directory.
5255                         Runtime.getRuntime().exec("rm -rf $privateDataDirectoryString/cache")
5256                     } catch (exception: IOException) {
5257                         // Do nothing if an error is thrown.
5258                     }
5259
5260                     // Clear the logcat.
5261                     try {
5262                         // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
5263                         Runtime.getRuntime().exec("logcat -b all -c")
5264                     } catch (exception: IOException) {
5265                         // Do nothing.
5266                     }
5267                 }
5268
5269                 // Clear the `Service Worker` directory.
5270                 try {
5271                     // A string array must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
5272                     Runtime.getRuntime().exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Service Worker/"))
5273                 } catch (exception: IOException) {
5274                     // Do nothing.
5275                 }
5276
5277                 // Get the current page position.
5278                 val currentPagePosition = webViewPagerAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
5279
5280                 // Get the current URL from the nested scroll WebView.  This is more accurate than using the URL passed into the method, which is sometimes not the final one.
5281                 val currentUrl = nestedScrollWebView.url
5282
5283                 // Get the current tab.
5284                 val tab = tabLayout.getTabAt(currentPagePosition)
5285
5286                 // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
5287                 // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
5288                 // Probably some sort of race condition when Privacy Browser is being resumed.
5289                 if ((tabLayout.selectedTabPosition == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) {
5290                     // Check to see if the URL is `about:blank`.
5291                     if (currentUrl == "about:blank") {  // The WebView is blank.
5292                         // Display the hint in the URL edit text.
5293                         urlEditText.setText("")
5294
5295                         // Request focus for the URL text box.
5296                         urlEditText.requestFocus()
5297
5298                         // Display the keyboard.
5299                         inputMethodManager.showSoftInput(urlEditText, 0)
5300
5301                         // Apply the domain settings.  This clears any settings from the previous domain.
5302                         applyDomainSettings(nestedScrollWebView, "", resetTab = true, reloadWebsite = false, loadUrl = false)
5303
5304                         // Only populate the title text view if the tab has been fully created.
5305                         if (tab != null) {
5306                             // Get the custom view from the tab.
5307                             val tabView = tab.customView!!
5308
5309                             // Get the title text view from the tab.
5310                             val tabTitleTextView = tabView.findViewById<TextView>(R.id.title_textview)
5311
5312                             // Set the title as the tab text.
5313                             tabTitleTextView.setText(R.string.new_tab)
5314                         }
5315                     } else {  // The WebView has loaded a webpage.
5316                         // Update the URL edit text if it is not currently being edited.
5317                         if (!urlEditText.hasFocus()) {
5318                             // Sanitize the current URL.  This removes unwanted URL elements that were added by redirects, so that they won't be included if the URL is shared.
5319                             val sanitizedUrl = sanitizeUrl(currentUrl)
5320
5321                             // Display the final URL.  Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
5322                             urlEditText.setText(sanitizedUrl)
5323
5324                             // Highlight the URL syntax.
5325                             UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
5326                         }
5327
5328                         // Only populate the title text view if the tab has been fully created.
5329                         if (tab != null) {
5330                             // Get the custom view from the tab.
5331                             val tabView = tab.customView!!
5332
5333                             // Get the title text view from the tab.
5334                             val tabTitleTextView = tabView.findViewById<TextView>(R.id.title_textview)
5335
5336                             // Set the title as the tab text.  Sometimes `onReceivedTitle()` is not called, especially when navigating history.
5337                             tabTitleTextView.text = nestedScrollWebView.title
5338                         }
5339                     }
5340                 }
5341             }
5342
5343             // Handle SSL Certificate errors.  Suppress the lint warning that ignoring the error might be dangerous.
5344             @SuppressLint("WebViewClientOnReceivedSslError")
5345             override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
5346                 // Get the current website SSL certificate.
5347                 val currentWebsiteSslCertificate = error.certificate
5348
5349                 // Extract the individual pieces of information from the current website SSL certificate.
5350                 val currentWebsiteIssuedToCName = currentWebsiteSslCertificate.issuedTo.cName
5351                 val currentWebsiteIssuedToOName = currentWebsiteSslCertificate.issuedTo.oName
5352                 val currentWebsiteIssuedToUName = currentWebsiteSslCertificate.issuedTo.uName
5353                 val currentWebsiteIssuedByCName = currentWebsiteSslCertificate.issuedBy.cName
5354                 val currentWebsiteIssuedByOName = currentWebsiteSslCertificate.issuedBy.oName
5355                 val currentWebsiteIssuedByUName = currentWebsiteSslCertificate.issuedBy.uName
5356                 val currentWebsiteSslStartDate = currentWebsiteSslCertificate.validNotBeforeDate
5357                 val currentWebsiteSslEndDate = currentWebsiteSslCertificate.validNotAfterDate
5358
5359                 // Get the pinned SSL certificate.
5360                 val (pinnedSslCertificateStringArray, pinnedSslCertificateDateArray) = nestedScrollWebView.getPinnedSslCertificate()
5361
5362                 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
5363                 if (nestedScrollWebView.hasPinnedSslCertificate() &&
5364                     (currentWebsiteIssuedToCName == pinnedSslCertificateStringArray[0]) &&
5365                     (currentWebsiteIssuedToOName == pinnedSslCertificateStringArray[1]) &&
5366                     (currentWebsiteIssuedToUName == pinnedSslCertificateStringArray[2]) &&
5367                     (currentWebsiteIssuedByCName == pinnedSslCertificateStringArray[3]) &&
5368                     (currentWebsiteIssuedByOName == pinnedSslCertificateStringArray[4]) &&
5369                     (currentWebsiteIssuedByUName == pinnedSslCertificateStringArray[5]) &&
5370                     (currentWebsiteSslStartDate == pinnedSslCertificateDateArray[0]) &&
5371                     (currentWebsiteSslEndDate == pinnedSslCertificateDateArray[1])) {
5372
5373                     // An SSL certificate is pinned and matches the current domain certificate.  Proceed to the website without displaying an error.
5374                     handler.proceed()
5375                 } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
5376                     // Store the SSL error handler.
5377                     nestedScrollWebView.sslErrorHandler = handler
5378
5379                     // Instantiate an SSL certificate error alert dialog.
5380                     val sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.webViewFragmentId)
5381
5382                     // Try to show the dialog.  The SSL error handler continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
5383                     try {
5384                         // Show the SSL certificate error dialog.
5385                         sslCertificateErrorDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate_error))
5386                     } catch (exception: Exception) {
5387                         // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
5388                         pendingDialogsArrayList.add(PendingDialogDataClass(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)))
5389                     }
5390                 }
5391             }
5392         }
5393
5394         // Check to see if the state is being restored.
5395         if (restoringState) {  // The state is being restored.
5396             // Resume the nested scroll WebView JavaScript timers.
5397             nestedScrollWebView.resumeTimers()
5398         } else if (pageNumber == 0) {  // The first page is being loaded.
5399             // Set this nested scroll WebView as the current WebView.
5400             currentWebView = nestedScrollWebView
5401
5402             // Get the intent that started the app.
5403             val launchingIntent = intent
5404
5405             // Reset the intent.  This prevents a duplicate tab from being created on restart.
5406             intent = Intent()
5407
5408             // Get the information from the intent.
5409             val launchingIntentAction = launchingIntent.action
5410             val launchingIntentUriData = launchingIntent.data
5411             val launchingIntentStringExtra = launchingIntent.getStringExtra(Intent.EXTRA_TEXT)
5412
5413             // Parse the launching intent URL.  Suppress the suggestions of using elvis expressions as they make the logic very difficult to follow.
5414             @Suppress("IfThenToElvis") val urlToLoadString = if ((launchingIntentAction != null) && (launchingIntentAction == Intent.ACTION_WEB_SEARCH)) {  // The intent contains a search string.
5415                 // Sanitize the search input and convert it to a search.
5416                 val encodedSearchString = try {
5417                     URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8")
5418                 } catch (exception: UnsupportedEncodingException) {
5419                     ""
5420                 }
5421
5422                 // Add the search URL to the encodedSearchString
5423                 searchURL + encodedSearchString
5424             } else if (launchingIntentUriData != null) {  // The launching intent contains a URL formatted as a URI.
5425                 // Get the URL from the URI.
5426                 launchingIntentUriData.toString()
5427             } else if (launchingIntentStringExtra != null) {  // The launching intent contains text that might be a URL.
5428                 // Get the URL from the string extra.
5429                 launchingIntentStringExtra
5430             } else if (urlString != "") {  // The activity has been restarted.
5431                 // Load the saved URL.
5432                 urlString
5433             } else {  // The is no saved URL and there is no URL in the intent.
5434                 // Load the homepage.
5435                 sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))
5436             }
5437
5438             // Load the website if not waiting for the proxy.
5439             if (waitingForProxy) {  // Store the URL to be loaded in the Nested Scroll WebView.
5440                 nestedScrollWebView.waitingForProxyUrlString = urlToLoadString!!
5441             } else {  // Load the URL.
5442                 loadUrl(nestedScrollWebView, urlToLoadString!!)
5443             }
5444
5445             // Reset the intent.  This prevents a duplicate tab from being created on a subsequent restart if loading an link from a new intent on restart.
5446             // For example, this prevents a duplicate tab if a link is loaded from the Guide after changing the theme in the guide and then changing the theme again in the main activity.
5447             intent = Intent()
5448         } else {  // This is not the first tab.
5449             // Load the URL.
5450             loadUrl(nestedScrollWebView, urlString)
5451
5452             // Set the focus and display the keyboard if the URL is blank.
5453             if (urlString == "") {
5454                 // Request focus for the URL text box.
5455                 urlEditText.requestFocus()
5456
5457                 // Create a display keyboard handler.
5458                 val displayKeyboardHandler = Handler(Looper.getMainLooper())
5459
5460                 // Create a display keyboard runnable.
5461                 val displayKeyboardRunnable = Runnable {
5462                     // Display the keyboard.
5463                     inputMethodManager.showSoftInput(urlEditText, 0)
5464                 }
5465
5466                 // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition.
5467                 displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100)
5468             }
5469         }
5470     }
5471
5472     private fun loadBookmarksFolder() {
5473         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
5474         bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolder)
5475
5476         // Populate the bookmarks cursor adapter.
5477         bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
5478             override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
5479                 // Inflate the individual item layout.
5480                 return layoutInflater.inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false)
5481             }
5482
5483             override fun bindView(view: View, context: Context, cursor: Cursor) {
5484                 // Get handles for the views.
5485                 val bookmarkFavoriteIcon = view.findViewById<ImageView>(R.id.bookmark_favorite_icon)
5486                 val bookmarkNameTextView = view.findViewById<TextView>(R.id.bookmark_name)
5487
5488                 // Get the favorite icon byte array from the cursor.
5489                 val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON))
5490
5491                 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
5492                 val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
5493
5494                 // Display the bitmap in the bookmark favorite icon.
5495                 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap)
5496
5497                 // Display the bookmark name from the cursor in the bookmark name text view.
5498                 bookmarkNameTextView.text = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME))
5499
5500                 // Make the font bold for folders.
5501                 if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1)
5502                     bookmarkNameTextView.typeface = Typeface.DEFAULT_BOLD
5503                 else  // Reset the font to default for normal bookmarks.
5504                     bookmarkNameTextView.typeface = Typeface.DEFAULT
5505             }
5506         }
5507
5508         // Populate the list view with the adapter.
5509         bookmarksListView.adapter = bookmarksCursorAdapter
5510
5511         // Set the bookmarks drawer title.
5512         if (currentBookmarksFolder.isEmpty())
5513             bookmarksTitleTextView.setText(R.string.bookmarks)
5514         else
5515             bookmarksTitleTextView.text = currentBookmarksFolder
5516     }
5517
5518     private fun loadUrl(nestedScrollWebView: NestedScrollWebView, url: String) {
5519         // Sanitize the URL.
5520         val urlString = sanitizeUrl(url)
5521
5522         // Apply the domain settings and load the URL.
5523         applyDomainSettings(nestedScrollWebView, urlString, resetTab = true, reloadWebsite = false, loadUrl = true)
5524     }
5525
5526     private fun loadUrlFromTextBox() {
5527         // Get the text from URL text box and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
5528         var unformattedUrlString = urlEditText.text.toString().trim { it <= ' ' }
5529
5530         // Create the formatted URL string.
5531         var urlString = ""
5532
5533         // Check to see if the unformatted URL string is a valid URL.  Otherwise, convert it into a search.
5534         if (unformattedUrlString.startsWith("content://")) {  // This is a content URL.
5535             // Load the entire content URL.
5536             urlString = unformattedUrlString
5537         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
5538             unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
5539
5540             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
5541             if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://"))
5542                 unformattedUrlString = "https://$unformattedUrlString"
5543
5544             // Initialize the unformatted URL.
5545             var unformattedUrl: URL? = null
5546
5547             // Convert the unformatted URL string to a URL.
5548             try {
5549                 unformattedUrl = URL(unformattedUrlString)
5550             } catch (exception: MalformedURLException) {
5551                 exception.printStackTrace()
5552             }
5553
5554             // Get the components of the URL.
5555             val scheme = unformattedUrl?.protocol
5556             val authority = unformattedUrl?.authority
5557             val path = unformattedUrl?.path
5558             val query = unformattedUrl?.query
5559             val fragment = unformattedUrl?.ref
5560
5561             // Create a URI.
5562             val uri = Uri.Builder()
5563
5564             // Build the URI from the components of the URL.
5565             uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment)
5566
5567             // Decode the URI as a UTF-8 string in.
5568             try {
5569                 urlString = URLDecoder.decode(uri.build().toString(), "UTF-8")
5570             } catch (exception: UnsupportedEncodingException) {
5571                 // Do nothing.  The formatted URL string will remain blank.
5572             }
5573         } else if (unformattedUrlString.isNotEmpty()) {  // This is not a URL, but rather a search string.
5574             // Sanitize the search input.
5575             val encodedSearchString = try {
5576                 URLEncoder.encode(unformattedUrlString, "UTF-8")
5577             } catch (exception: UnsupportedEncodingException) {
5578                 ""
5579             }
5580
5581             // Add the base search URL.
5582             urlString = searchURL + encodedSearchString
5583         }
5584
5585         // Clear the focus from the URL edit text.  Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
5586         urlEditText.clearFocus()
5587
5588         // Make it so.
5589         loadUrl(currentWebView!!, urlString)
5590     }
5591
5592     override fun navigateHistory(url: String, steps: Int) {
5593         // Apply the domain settings.
5594         applyDomainSettings(currentWebView!!, url, resetTab = false, reloadWebsite = false, loadUrl = false)
5595
5596         // Load the history entry.
5597         currentWebView!!.goBackOrForward(steps)
5598     }
5599
5600     override fun openFile(dialogFragment: DialogFragment) {
5601         // Get the dialog.
5602         val dialog = dialogFragment.dialog!!
5603
5604         // Get handles for the views.
5605         val fileNameEditText = dialog.findViewById<EditText>(R.id.file_name_edittext)
5606         val mhtCheckBox = dialog.findViewById<CheckBox>(R.id.mht_checkbox)
5607
5608         // Get the file path string.
5609         val openFilePath = fileNameEditText.text.toString()
5610
5611         // Apply the domain settings.  This resets the favorite icon and removes any domain settings.
5612         applyDomainSettings(currentWebView!!, openFilePath, resetTab = true, reloadWebsite = false, loadUrl = false)
5613
5614         // Open the file according to the type.
5615         if (mhtCheckBox.isChecked) {  // Force opening of an MHT file.
5616             try {
5617                 // Get the MHT file input stream.
5618                 val mhtFileInputStream = contentResolver.openInputStream(Uri.parse(openFilePath))
5619
5620                 // Create a temporary MHT file.
5621                 val temporaryMhtFile = File.createTempFile(TEMPORARY_MHT_FILE, ".mht", cacheDir)
5622
5623                 // Get a file output stream for the temporary MHT file.
5624                 val temporaryMhtFileOutputStream = FileOutputStream(temporaryMhtFile)
5625
5626                 // Create a transfer byte array.
5627                 val transferByteArray = ByteArray(1024)
5628
5629                 // Create an integer to track the number of bytes read.
5630                 var bytesRead: Int
5631
5632                 // Copy the temporary MHT file input stream to the MHT output stream.
5633                 while (mhtFileInputStream!!.read(transferByteArray).also { bytesRead = it } > 0)
5634                     temporaryMhtFileOutputStream.write(transferByteArray, 0, bytesRead)
5635
5636                 // Flush the temporary MHT file output stream.
5637                 temporaryMhtFileOutputStream.flush()
5638
5639                 // Close the streams.
5640                 temporaryMhtFileOutputStream.close()
5641                 mhtFileInputStream.close()
5642
5643                 // Load the temporary MHT file.
5644                 currentWebView!!.loadUrl(temporaryMhtFile.toString())
5645             } catch (exception: Exception) {
5646                 // Display a snackbar.
5647                 Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
5648             }
5649         } else {  // Let the WebView handle opening of the file.
5650             // Open the file.
5651             currentWebView!!.loadUrl(openFilePath)
5652         }
5653     }
5654
5655     private fun openWithApp(url: String) {
5656         // Create an open with app intent with `ACTION_VIEW`.
5657         val openWithAppIntent = Intent(Intent.ACTION_VIEW)
5658
5659         // Set the URI but not the MIME type.  This should open all available apps.
5660         openWithAppIntent.data = Uri.parse(url)
5661
5662         // Flag the intent to open in a new task.
5663         openWithAppIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
5664
5665         // Try the intent.
5666         try {
5667             // Show the chooser.
5668             startActivity(openWithAppIntent)
5669         } catch (exception: ActivityNotFoundException) {  // There are no apps available to open the URL.
5670             // Show a snackbar with the error.
5671             Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
5672         }
5673     }
5674
5675     private fun openWithBrowser(url: String) {
5676
5677         // Create an open with browser intent with `ACTION_VIEW`.
5678         val openWithBrowserIntent = Intent(Intent.ACTION_VIEW)
5679
5680         // Set the URI and the MIME type.  `"text/html"` should load browser options.
5681         openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html")
5682
5683         // Flag the intent to open in a new task.
5684         openWithBrowserIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
5685
5686         // Try the intent.
5687         try {
5688             // Show the chooser.
5689             startActivity(openWithBrowserIntent)
5690         } catch (exception: ActivityNotFoundException) {  // There are no browsers available to open the URL.
5691             // Show a snackbar with the error.
5692             Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
5693         }
5694     }
5695
5696     override fun pinnedErrorGoBack() {
5697         // Get the current web back forward list.
5698         val webBackForwardList = currentWebView!!.copyBackForwardList()
5699
5700         // Get the previous entry URL.
5701         val previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.currentIndex - 1).url
5702
5703         // Apply the domain settings.
5704         applyDomainSettings(currentWebView!!, previousUrl, resetTab = false, reloadWebsite = false, loadUrl = false)
5705
5706         // Go back.
5707         currentWebView!!.goBack()
5708     }
5709
5710     private fun sanitizeUrl(urlString: String): String {
5711         // Initialize a sanitized URL string.
5712         var sanitizedUrlString = urlString
5713
5714         // Sanitize tracking queries.
5715         if (sanitizeTrackingQueries)
5716             sanitizedUrlString = SanitizeUrlHelper.sanitizeTrackingQueries(sanitizedUrlString)
5717
5718         // Sanitize AMP redirects.
5719         if (sanitizeAmpRedirects)
5720             sanitizedUrlString = SanitizeUrlHelper.sanitizeAmpRedirects(sanitizedUrlString)
5721
5722         // Return the sanitized URL string.
5723         return sanitizedUrlString
5724     }
5725
5726     override fun saveUrl(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment) {
5727         // Store the URL.  This will be used in the save URL activity result launcher.
5728         saveUrlString = if (originalUrlString.startsWith("data:")) {
5729             // Save the original URL.
5730             originalUrlString
5731         } else {
5732             // Get the dialog.
5733             val dialog = dialogFragment.dialog!!
5734
5735             // Get a handle for the dialog URL edit text.
5736             val dialogUrlEditText = dialog.findViewById<EditText>(R.id.url_edittext)
5737
5738             // Get the URL from the edit text, which may have been modified.
5739             dialogUrlEditText.text.toString()
5740         }
5741
5742         // Open the file picker.
5743         saveUrlActivityResultLauncher.launch(fileNameString)
5744     }
5745
5746     private fun setCurrentWebView(pageNumber: Int) {
5747         // Stop the swipe to refresh indicator if it is running
5748         swipeRefreshLayout.isRefreshing = false
5749
5750         // Get the WebView tab fragment.
5751         val webViewTabFragment = webViewPagerAdapter!!.getPageFragment(pageNumber)
5752
5753         // Get the fragment view.
5754         val webViewFragmentView = webViewTabFragment.view
5755
5756         // Set the current WebView if the fragment view is not null.
5757         if (webViewFragmentView != null) {  // The fragment has been populated.
5758             // Store the current WebView.
5759             currentWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview)
5760
5761             // Update the status of swipe to refresh.
5762             if (currentWebView!!.swipeToRefresh) {  // Swipe to refresh is enabled.
5763                 // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.  It is updated every time the scroll changes.
5764                 swipeRefreshLayout.isEnabled = (currentWebView!!.scrollY == 0)
5765             } else {  // Swipe to refresh is disabled.
5766                 // Disable the swipe refresh layout.
5767                 swipeRefreshLayout.isEnabled = false
5768             }
5769
5770             // Set the cookie status.
5771             cookieManager.setAcceptCookie(currentWebView!!.acceptCookies)
5772
5773             // Update the privacy icons.  `true` redraws the icons in the app bar.
5774             updatePrivacyIcons(true)
5775
5776             // Get a handle for the input method manager.
5777             val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
5778
5779             // Get the current URL.
5780             val urlString = currentWebView!!.url
5781
5782             // Update the URL edit text if not loading a new intent.  Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
5783             if (!loadingNewIntent) {  // A new intent is not being loaded.
5784                 if ((urlString == null) || (urlString == "about:blank")) {  // The WebView is blank.
5785                     // Display the hint in the URL edit text.
5786                     urlEditText.setText("")
5787
5788                     // Request focus for the URL text box.
5789                     urlEditText.requestFocus()
5790
5791                     // Display the keyboard.
5792                     inputMethodManager.showSoftInput(urlEditText, 0)
5793                 } else {  // The WebView has a loaded URL.
5794                     // Clear the focus from the URL text box.
5795                     urlEditText.clearFocus()
5796
5797                     // Hide the soft keyboard.
5798                     inputMethodManager.hideSoftInputFromWindow(currentWebView!!.windowToken, 0)
5799
5800                     // Display the current URL in the URL text box.
5801                     urlEditText.setText(urlString)
5802
5803                     // Highlight the URL syntax.
5804                     UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
5805                 }
5806             } else {  // A new intent is being loaded.
5807                 // Reset the loading new intent flag.
5808                 loadingNewIntent = false
5809             }
5810
5811             // Set the background to indicate the domain settings status.
5812             if (currentWebView!!.domainSettingsApplied) {
5813                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
5814                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.drawable.domain_settings_url_background)
5815             } else {
5816                 // Remove any background on the URL relative layout.
5817                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.color.transparent)
5818             }
5819         } else if (pageNumber == savedTabPosition) {  // The app is being restored but the saved tab position fragment has not been populated yet.  Try again in 100 milliseconds.
5820             // Create a handler to set the current WebView.
5821             val setCurrentWebViewHandler = Handler(Looper.getMainLooper())
5822
5823             // Create a runnable to set the current WebView.
5824             val setCurrentWebWebRunnable = Runnable {
5825                 // Set the current WebView.
5826                 setCurrentWebView(pageNumber)
5827             }
5828
5829             // Try setting the current WebView again after 100 milliseconds.
5830             setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100)
5831         }
5832     }
5833
5834     // The view parameter cannot be removed because it is called from the layout onClick.
5835     fun toggleBookmarksDrawerPinned(@Suppress("UNUSED_PARAMETER")view: View?) {
5836         // Toggle the bookmarks drawer pinned tracker.
5837         bookmarksDrawerPinned = !bookmarksDrawerPinned
5838
5839         // Update the bookmarks drawer pinned image view.
5840         updateBookmarksDrawerPinnedImageView()
5841     }
5842
5843     private fun updateBookmarksDrawerPinnedImageView() {
5844         // Set the current icon.
5845         if (bookmarksDrawerPinned)
5846             bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin_selected)
5847         else
5848             bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin)
5849     }
5850
5851     private fun updateDomainsSettingsSet() {
5852         // Reset the domains settings set.
5853         domainsSettingsSet = HashSet()
5854
5855         // Get a domains cursor.
5856         val domainsCursor = domainsDatabaseHelper!!.domainNameCursorOrderedByDomain
5857
5858         // Get the current count of domains.
5859         val domainsCount = domainsCursor.count
5860
5861         // Get the domain name column index.
5862         val domainNameColumnIndex = domainsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME)
5863
5864         // Populate the domain settings set.
5865         for (i in 0 until domainsCount) {
5866             // Move the domains cursor to the current row.
5867             domainsCursor.moveToPosition(i)
5868
5869             // Store the domain name in the domain settings set.
5870             domainsSettingsSet.add(domainsCursor.getString(domainNameColumnIndex))
5871         }
5872
5873         // Close the domains cursor.
5874         domainsCursor.close()
5875     }
5876
5877     override fun updateFontSize(dialogFragment: DialogFragment) {
5878         // Get the dialog.
5879         val dialog = dialogFragment.dialog!!
5880
5881         // Get a handle for the font size edit text.
5882         val fontSizeEditText = dialog.findViewById<EditText>(R.id.font_size_edittext)
5883
5884         // Initialize the new font size variable with the current font size.
5885         var newFontSize = currentWebView!!.settings.textZoom
5886
5887         // Get the font size from the edit text.
5888         try {
5889             newFontSize = fontSizeEditText.text.toString().toInt()
5890         } catch (exception: Exception) {
5891             // If the edit text does not contain a valid font size do nothing.
5892         }
5893
5894         // Apply the new font size.
5895         currentWebView!!.settings.textZoom = newFontSize
5896     }
5897
5898     private fun updatePrivacyIcons(runInvalidateOptionsMenu: Boolean) {
5899         // Only update the privacy icons if the options menu and the current WebView have already been populated.
5900         if ((optionsMenu != null) && (currentWebView != null)) {
5901             // Update the privacy icon.
5902             if (currentWebView!!.settings.javaScriptEnabled)  // JavaScript is enabled.
5903                 optionsPrivacyMenuItem.setIcon(R.drawable.javascript_enabled)
5904             else if (currentWebView!!.acceptCookies)  // JavaScript is disabled but cookies are enabled.
5905                 optionsPrivacyMenuItem.setIcon(R.drawable.warning)
5906             else  // All the dangerous features are disabled.
5907                 optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode)
5908
5909             // Update the cookies icon.
5910             if (currentWebView!!.acceptCookies)
5911                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled)
5912             else
5913                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled)
5914
5915             // Update the refresh icon.
5916             if (optionsRefreshMenuItem.title == getString(R.string.refresh))  // The refresh icon is displayed.
5917                 optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled)
5918             else  // The stop icon is displayed.
5919                 optionsRefreshMenuItem.setIcon(R.drawable.close_blue)
5920
5921             // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar.
5922             if (runInvalidateOptionsMenu)
5923                 invalidateOptionsMenu()
5924         }
5925     }
5926 }