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