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