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