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