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