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