]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
Replace `Loading...` and favorite icon on tab after download dialog opens. https...
[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!!, 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)
2010                 } else {  // The rendered website is currently viewed.
2011                     // Open the source in a new tab.
2012                     addNewTab("view-source:${currentWebView!!.currentUrl}", 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, 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, 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, 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, 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, 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, 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("", true)
2969     }
2970
2971     private fun addNewTab(urlString: String, 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 page number.  The page numbers are 0 indexed, so the new page number will match the current count.
2976         val newTabNumber = tabLayout.tabCount
2977
2978         // Add a new tab.
2979         tabLayout.addTab(tabLayout.newTab())
2980
2981         // Get the new tab.
2982         val newTab = tabLayout.getTabAt(newTabNumber)!!
2983
2984         // Set a custom view on the new tab.
2985         newTab.setCustomView(R.layout.tab_custom_view)
2986
2987         // Scroll to the new tab position.
2988         tabLayout.post { tabLayout.setScrollPosition(newTabNumber, 0F, false, false) }
2989
2990         // Add the new WebView page.
2991         webViewStateAdapter!!.addPage(newTabNumber, newTab, urlString, moveToTab)
2992
2993         // Show the app bar if it is at the bottom of the screen and the new tab is taking focus.
2994         if (bottomAppBar && moveToTab && appBarLayout.translationY != 0f) {
2995             // Animate the bottom app bar onto the screen.
2996             objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0f)
2997
2998             // Make it so.
2999             objectAnimator.start()
3000         }
3001     }
3002
3003     private fun applyAppSettings() {
3004         // 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.
3005         defaultJavaScript = sharedPreferences.getBoolean(getString(R.string.javascript_key), false)
3006         defaultCookies = sharedPreferences.getBoolean(getString(R.string.cookies_key), false)
3007         defaultDomStorage = sharedPreferences.getBoolean(getString(R.string.dom_storage_key), false)
3008         defaultFormData = sharedPreferences.getBoolean(getString(R.string.save_form_data_key), false)  // Form data can be removed once the minimum API >= 26.
3009         defaultEasyList = sharedPreferences.getBoolean(getString(R.string.easylist_key), true)
3010         defaultEasyPrivacy = sharedPreferences.getBoolean(getString(R.string.easyprivacy_key), true)
3011         defaultFanboysAnnoyanceList = sharedPreferences.getBoolean(getString(R.string.fanboys_annoyance_list_key), true)
3012         defaultFanboysSocialBlockingList = sharedPreferences.getBoolean(getString(R.string.fanboys_social_blocking_list_key), true)
3013         defaultUltraList = sharedPreferences.getBoolean(getString(R.string.ultralist_key), true)
3014         defaultUltraPrivacy = sharedPreferences.getBoolean(getString(R.string.ultraprivacy_key), true)
3015         defaultBlockAllThirdPartyRequests = sharedPreferences.getBoolean(getString(R.string.block_all_third_party_requests_key), false)
3016         defaultFontSizeString = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value))!!
3017         defaultUserAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value))!!
3018         defaultSwipeToRefresh = sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true)
3019         defaultWebViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value))!!
3020         defaultWideViewport = sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true)
3021         defaultDisplayWebpageImages = sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true)
3022
3023         // 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.
3024         webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values)
3025
3026         // Get the user agent string arrays.  These are done here so that expensive resource requests are not made each time a domain is loaded.
3027         userAgentDataArray = resources.getStringArray(R.array.user_agent_data)
3028         userAgentNamesArray = resources.getStringArray(R.array.user_agent_names)
3029
3030         // Get the user agent array adapters.  These are done here so that expensive resource requests are not made each time a domain is loaded.
3031         userAgentDataArrayAdapter = ArrayAdapter.createFromResource(this, R.array.user_agent_data, R.layout.spinner_item)
3032         userAgentNamesArrayAdapter = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item)
3033
3034         // Store the values from the shared preferences in variables.
3035         incognitoModeEnabled = sharedPreferences.getBoolean(getString(R.string.incognito_mode_key), false)
3036         sanitizeTrackingQueries = sharedPreferences.getBoolean(getString(R.string.tracking_queries_key), true)
3037         sanitizeAmpRedirects = sharedPreferences.getBoolean(getString(R.string.amp_redirects_key), true)
3038         proxyMode = sharedPreferences.getString(getString(R.string.proxy_key), getString(R.string.proxy_default_value))!!
3039         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean(getString(R.string.full_screen_browsing_mode_key), false)
3040         hideAppBar = sharedPreferences.getBoolean(getString(R.string.hide_app_bar_key), true)
3041         downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false)
3042         scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), true)
3043
3044         // Apply the saved proxy mode if the app has been restarted.
3045         if (savedProxyMode != null) {
3046             // Apply the saved proxy mode.
3047             proxyMode = savedProxyMode!!
3048
3049             // Reset the saved proxy mode.
3050             savedProxyMode = null
3051         }
3052
3053         // Get the search string.
3054         val searchString = sharedPreferences.getString(getString(R.string.search_key), getString(R.string.search_default_value))!!
3055
3056         // Set the search string, using the custom search URL if specified.
3057         searchURL = if (searchString == getString(R.string.custom_url_item))
3058             sharedPreferences.getString(getString(R.string.search_custom_url_key), getString(R.string.search_custom_url_default_value))!!
3059         else
3060             searchString
3061
3062         // Apply the proxy.
3063         applyProxy(false)
3064
3065         // Adjust the layout and scrolling parameters according to the position of the app bar.
3066         if (bottomAppBar) {  // The app bar is on the bottom.
3067             // Adjust the UI.
3068             if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
3069                 // Reset the WebView padding to fill the available space.
3070                 swipeRefreshLayout.setPadding(0, 0, 0, 0)
3071             } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
3072                 // Move the WebView above the app bar layout.
3073                 swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight)
3074
3075                 // Show the app bar if it is scrolled off the screen.
3076                 if (appBarLayout.translationY != 0f) {
3077                     // Animate the bottom app bar onto the screen.
3078                     objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0f)
3079
3080                     // Make it so.
3081                     objectAnimator.start()
3082                 }
3083             }
3084         } else {  // The app bar is on the top.
3085             // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
3086             val swipeRefreshLayoutParams = swipeRefreshLayout.layoutParams as CoordinatorLayout.LayoutParams
3087             val toolbarLayoutParams = toolbar.layoutParams as AppBarLayout.LayoutParams
3088             val findOnPageLayoutParams = findOnPageLinearLayout.layoutParams as AppBarLayout.LayoutParams
3089             val tabsLayoutParams = tabsLinearLayout.layoutParams as AppBarLayout.LayoutParams
3090
3091             // Add the scrolling behavior to the layout parameters.
3092             if (scrollAppBar) {
3093                 // Enable scrolling of the app bar.
3094                 swipeRefreshLayoutParams.behavior = AppBarLayout.ScrollingViewBehavior()
3095                 toolbarLayoutParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS or AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
3096                 findOnPageLayoutParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS or AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
3097                 tabsLayoutParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS or AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
3098             } else {
3099                 // Disable scrolling of the app bar.
3100                 swipeRefreshLayoutParams.behavior = null
3101                 toolbarLayoutParams.scrollFlags = 0
3102                 findOnPageLayoutParams.scrollFlags = 0
3103                 tabsLayoutParams.scrollFlags = 0
3104
3105                 // Expand the app bar if it is currently collapsed.
3106                 appBarLayout.setExpanded(true)
3107             }
3108
3109             // Set the app bar scrolling for each WebView.
3110             for (i in 0 until webViewStateAdapter!!.itemCount) {
3111                 // Get the WebView tab fragment.
3112                 val webViewTabFragment = webViewStateAdapter!!.getPageFragment(i)
3113
3114                 // Get the fragment view.
3115                 val fragmentView = webViewTabFragment.view
3116
3117                 // Only modify the WebViews if they exist.
3118                 if (fragmentView != null) {
3119                     // Get the nested scroll WebView from the tab fragment.
3120                     val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
3121
3122                     // Set the app bar scrolling.
3123                     nestedScrollWebView.isNestedScrollingEnabled = scrollAppBar
3124                 }
3125             }
3126         }
3127
3128         // Update the full screen browsing mode settings.
3129         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
3130             // Update the visibility of the app bar, which might have changed in the settings.
3131             if (hideAppBar) {
3132                 // Hide the tab linear layout.
3133                 tabsLinearLayout.visibility = View.GONE
3134
3135                 // Hide the app bar.
3136                 appBar.hide()
3137             } else {
3138                 // Show the tab linear layout.
3139                 tabsLinearLayout.visibility = View.VISIBLE
3140
3141                 // Show the app bar.
3142                 appBar.show()
3143             }
3144
3145             /* Hide the system bars.
3146              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
3147              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
3148              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
3149              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
3150              */
3151
3152             // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
3153             @Suppress("DEPRECATION")
3154             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
3155         } else {  // Privacy Browser is not in full screen browsing mode.
3156             // 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.
3157             inFullScreenBrowsingMode = false
3158
3159             // Show the tab linear layout.
3160             tabsLinearLayout.visibility = View.VISIBLE
3161
3162             // Show the app bar.
3163             appBar.show()
3164
3165             // Remove the `SYSTEM_UI` flags from the root frame layout.  The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
3166             @Suppress("DEPRECATION")
3167             rootFrameLayout.systemUiVisibility = 0
3168         }
3169     }
3170
3171     // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
3172     @SuppressLint("SetJavaScriptEnabled")
3173     private fun applyDomainSettings(nestedScrollWebView: NestedScrollWebView, url: String?, resetTab: Boolean, reloadWebsite: Boolean, loadUrl: Boolean) {
3174         // Store the current URL.
3175         nestedScrollWebView.currentUrl = url!!
3176
3177         // Parse the URL into a URI.
3178         val uri = Uri.parse(url)
3179
3180         // Extract the domain from the URI.
3181         var newHostName = uri.host
3182
3183         // Strings don't like to be null.
3184         if (newHostName == null)
3185             newHostName = ""
3186
3187         // 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.
3188         if ((nestedScrollWebView.currentDomainName != newHostName) || (newHostName == "")) {  // A new domain is being loaded.
3189             // Set the new host name as the current domain name.
3190             nestedScrollWebView.currentDomainName = newHostName
3191
3192             // Reset the ignoring of pinned domain information.
3193             nestedScrollWebView.ignorePinnedDomainInformation = false
3194
3195             // Clear any pinned SSL certificate or IP addresses.
3196             nestedScrollWebView.clearPinnedSslCertificate()
3197             nestedScrollWebView.pinnedIpAddresses = ""
3198
3199             // Reset the favorite icon if specified.
3200             if (resetTab) {
3201                 // Initialize the favorite icon.
3202                 nestedScrollWebView.initializeFavoriteIcon()
3203
3204                 // Get the current page position.
3205                 val currentPagePosition = webViewStateAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
3206
3207                 // Get the corresponding tab.
3208                 val tab = tabLayout.getTabAt(currentPagePosition)
3209
3210                 // Update the tab if it isn't null, which sometimes happens when restarting from the background.
3211                 if (tab != null) {
3212                     // Get the tab custom view.
3213                     val tabCustomView = tab.customView!!
3214
3215                     // Get the tab views.
3216                     val tabFavoriteIconImageView = tabCustomView.findViewById<ImageView>(R.id.favorite_icon_imageview)
3217                     val tabTitleTextView = tabCustomView.findViewById<TextView>(R.id.title_textview)
3218
3219                     // Store the current values in case they need to be restored.
3220                     nestedScrollWebView.previousFavoriteIconDrawable = tabFavoriteIconImageView.drawable
3221                     nestedScrollWebView.previousWebpageTitle = tabTitleTextView.text.toString()
3222
3223                     // Set the default favorite icon as the favorite icon for this tab.
3224                     tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteIcon(), 64, 64, true))
3225
3226                     // Set the loading title text.
3227                     tabTitleTextView.setText(R.string.loading)
3228                 }
3229             }
3230
3231             // Initialize the domain name in database variable.
3232             var domainNameInDatabase: String? = null
3233
3234             // Check the hostname against the domain settings set.
3235             if (domainsSettingsSet.contains(newHostName)) {  // The hostname is contained in the domain settings set.
3236                 // Record the domain name in the database.
3237                 domainNameInDatabase = newHostName
3238
3239                 // Set the domain settings applied tracker to true.
3240                 nestedScrollWebView.domainSettingsApplied = true
3241             } else {  // The hostname is not contained in the domain settings set.
3242                 // Set the domain settings applied tracker to false.
3243                 nestedScrollWebView.domainSettingsApplied = false
3244             }
3245
3246             // Check all the subdomains of the host name against wildcard domains in the domain cursor.
3247             while (!nestedScrollWebView.domainSettingsApplied && newHostName!!.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the hostname.
3248                 if (domainsSettingsSet.contains("*.$newHostName")) {  // Check the host name prepended by `*.`.
3249                     // Set the domain settings applied tracker to true.
3250                     nestedScrollWebView.domainSettingsApplied = true
3251
3252                     // Store the applied domain names as it appears in the database.
3253                     domainNameInDatabase = "*.$newHostName"
3254                 }
3255
3256                 // Strip out the lowest subdomain of of the host name.
3257                 newHostName = newHostName.substring(newHostName.indexOf(".") + 1)
3258             }
3259
3260             // Apply either the domain settings or the default settings.
3261             if (nestedScrollWebView.domainSettingsApplied) {  // The url has custom domain settings.
3262                 // Get a cursor for the current host.
3263                 val currentDomainSettingsCursor = domainsDatabaseHelper!!.getCursorForDomainName(domainNameInDatabase!!)
3264
3265                 // Move to the first position.
3266                 currentDomainSettingsCursor.moveToFirst()
3267
3268                 // Get the settings from the cursor.
3269                 nestedScrollWebView.domainSettingsDatabaseId = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ID))
3270                 val javaScriptInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ENABLE_JAVASCRIPT))
3271                 val cookiesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(COOKIES))
3272                 val domStorageInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ENABLE_DOM_STORAGE))
3273                 val formDataInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ENABLE_FORM_DATA))  // Form data can be removed once the minimum API >= 26.
3274                 val easyListInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ENABLE_EASYLIST))
3275                 val easyPrivacyInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ENABLE_EASYPRIVACY))
3276                 val fanboysAnnoyanceListInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ENABLE_FANBOYS_ANNOYANCE_LIST))
3277                 val fanboysSocialBlockingListInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST))
3278                 val ultraListInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(com.stoutner.privacybrowser.helpers.ULTRALIST))
3279                 val ultraPrivacyInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(ENABLE_ULTRAPRIVACY))
3280                 val blockAllThirdPartyRequestsInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(com.stoutner.privacybrowser.helpers.BLOCK_ALL_THIRD_PARTY_REQUESTS))
3281                 val userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(com.stoutner.privacybrowser.helpers.USER_AGENT))
3282                 val fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(FONT_SIZE))
3283                 val swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(SWIPE_TO_REFRESH))
3284                 val webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(WEBVIEW_THEME))
3285                 val wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(WIDE_VIEWPORT))
3286                 val displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DISPLAY_IMAGES))
3287                 val pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(PINNED_SSL_CERTIFICATE)) == 1)
3288                 val pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_COMMON_NAME))
3289                 val pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_ORGANIZATION))
3290                 val pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT))
3291                 val pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_COMMON_NAME))
3292                 val pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_ORGANIZATION))
3293                 val pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT))
3294                 val pinnedSslStartDate = Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(com.stoutner.privacybrowser.helpers.SSL_START_DATE)))
3295                 val pinnedSslEndDate = Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(com.stoutner.privacybrowser.helpers.SSL_END_DATE)))
3296                 val pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(PINNED_IP_ADDRESSES)) == 1)
3297                 val pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(IP_ADDRESSES))
3298
3299                 // Close the current host domain settings cursor.
3300                 currentDomainSettingsCursor.close()
3301
3302                 // Set the JavaScript status.
3303                 when (javaScriptInt) {
3304                     SYSTEM_DEFAULT -> nestedScrollWebView.settings.javaScriptEnabled = defaultJavaScript
3305                     ENABLED -> nestedScrollWebView.settings.javaScriptEnabled = true
3306                     DISABLED -> nestedScrollWebView.settings.javaScriptEnabled = false
3307                 }
3308
3309                 // Store the cookies status.
3310                 when (cookiesInt) {
3311                     SYSTEM_DEFAULT -> nestedScrollWebView.acceptCookies = defaultCookies
3312                     ENABLED -> nestedScrollWebView.acceptCookies = true
3313                     DISABLED -> nestedScrollWebView.acceptCookies = false
3314                 }
3315
3316                 // Apply the cookies status.
3317                 cookieManager.setAcceptCookie(nestedScrollWebView.acceptCookies)
3318
3319                 // Set the DOM storage status.
3320                 when (domStorageInt) {
3321                     SYSTEM_DEFAULT -> nestedScrollWebView.settings.domStorageEnabled = defaultDomStorage
3322                     ENABLED -> nestedScrollWebView.settings.domStorageEnabled = true
3323                     DISABLED -> nestedScrollWebView.settings.domStorageEnabled = false
3324                 }
3325
3326                 // Apply the form data setting if the API < 26.
3327                 @Suppress("DEPRECATION")
3328                 if (Build.VERSION.SDK_INT < 26) {
3329                     // Set the form data status.
3330                     when (formDataInt) {
3331                         SYSTEM_DEFAULT -> nestedScrollWebView.settings.saveFormData = defaultFormData
3332                         ENABLED -> nestedScrollWebView.settings.saveFormData = true
3333                         DISABLED -> nestedScrollWebView.settings.saveFormData = false
3334                     }
3335                 }
3336
3337                 // Set the EasyList status.
3338                 when (easyListInt) {
3339                     SYSTEM_DEFAULT -> nestedScrollWebView.easyListEnabled = defaultEasyList
3340                     ENABLED -> nestedScrollWebView.easyListEnabled = true
3341                     DISABLED -> nestedScrollWebView.easyListEnabled = false
3342                 }
3343
3344                 // Set the EasyPrivacy status.
3345                 when (easyPrivacyInt) {
3346                     SYSTEM_DEFAULT -> nestedScrollWebView.easyPrivacyEnabled = defaultEasyPrivacy
3347                     ENABLED -> nestedScrollWebView.easyPrivacyEnabled = true
3348                     DISABLED -> nestedScrollWebView.easyPrivacyEnabled = false
3349                 }
3350
3351                 // Set the Fanboy's Annoyance List status.
3352                 when (fanboysAnnoyanceListInt) {
3353                     SYSTEM_DEFAULT -> nestedScrollWebView.fanboysAnnoyanceListEnabled = defaultFanboysAnnoyanceList
3354                     ENABLED -> nestedScrollWebView.fanboysAnnoyanceListEnabled = true
3355                     DISABLED -> nestedScrollWebView.fanboysAnnoyanceListEnabled = false
3356                 }
3357
3358                 // Set the Fanboy's Social Blocking List status.
3359                 when (fanboysSocialBlockingListInt) {
3360                     SYSTEM_DEFAULT -> nestedScrollWebView.fanboysSocialBlockingListEnabled = defaultFanboysSocialBlockingList
3361                     ENABLED -> nestedScrollWebView.fanboysSocialBlockingListEnabled = true
3362                     DISABLED -> nestedScrollWebView.fanboysSocialBlockingListEnabled = false
3363                 }
3364
3365                 // Set the UltraList status.
3366                 when (ultraListInt) {
3367                     SYSTEM_DEFAULT -> nestedScrollWebView.ultraListEnabled = defaultUltraList
3368                     ENABLED -> nestedScrollWebView.ultraListEnabled = true
3369                     DISABLED -> nestedScrollWebView.ultraListEnabled = false
3370                 }
3371
3372                 // Set the UltraPrivacy status.
3373                 when (ultraPrivacyInt) {
3374                     SYSTEM_DEFAULT -> nestedScrollWebView.ultraPrivacyEnabled = defaultUltraPrivacy
3375                     ENABLED -> nestedScrollWebView.ultraPrivacyEnabled = true
3376                     DISABLED -> nestedScrollWebView.ultraPrivacyEnabled = false
3377                 }
3378
3379                 // Set the block all third-party requests status.
3380                 when (blockAllThirdPartyRequestsInt) {
3381                     SYSTEM_DEFAULT -> nestedScrollWebView.blockAllThirdPartyRequests = defaultBlockAllThirdPartyRequests
3382                     ENABLED -> nestedScrollWebView.blockAllThirdPartyRequests = true
3383                     DISABLED -> nestedScrollWebView.blockAllThirdPartyRequests = false
3384                 }
3385
3386                 // Set the user agent.
3387                 if (userAgentName == getString(R.string.system_default_user_agent)) {  // Use the system default user agent.
3388                     // Set the user agent according to the system default.
3389                     when (val defaultUserAgentArrayPosition = userAgentNamesArrayAdapter.getPosition(defaultUserAgentName)) {
3390                         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.
3391                             nestedScrollWebView.settings.userAgentString = defaultUserAgentName
3392
3393                         SETTINGS_WEBVIEW_DEFAULT_USER_AGENT ->  // Set the user agent to `""`, which uses the default value.
3394                             nestedScrollWebView.settings.userAgentString = ""
3395
3396                         SETTINGS_CUSTOM_USER_AGENT ->  // Set the default custom user agent.
3397                             nestedScrollWebView.settings.userAgentString = sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))
3398
3399                         else ->  // Get the user agent string from the user agent data array
3400                             nestedScrollWebView.settings.userAgentString = userAgentDataArray[defaultUserAgentArrayPosition]
3401                     }
3402                 } else {  // Set the user agent according to the stored name.
3403                     // Set the user agent.
3404                     when (val userAgentArrayPosition = userAgentNamesArrayAdapter.getPosition(userAgentName)) {
3405                         // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
3406                         UNRECOGNIZED_USER_AGENT ->
3407                             nestedScrollWebView.settings.userAgentString = userAgentName
3408
3409                         // Set the user agent to `""`, which uses the default value.
3410                         SETTINGS_WEBVIEW_DEFAULT_USER_AGENT ->
3411                             nestedScrollWebView.settings.userAgentString = ""
3412
3413                         // Get the user agent string from the user agent data array.
3414                         else ->
3415                             nestedScrollWebView.settings.userAgentString = userAgentDataArray[userAgentArrayPosition]
3416                     }
3417                 }
3418
3419                 // Apply the font size.
3420                 try {  // Try the specified font size to see if it is valid.
3421                     if (fontSize == 0) {  // Apply the default font size.
3422                         // Set the font size from the value in the app settings.
3423                         nestedScrollWebView.settings.textZoom = defaultFontSizeString.toInt()
3424                     } else {  // Apply the font size from domain settings.
3425                         nestedScrollWebView.settings.textZoom = fontSize
3426                     }
3427                 } catch (exception: Exception) {  // The specified font size is invalid
3428                     // Set the font size to be 100%
3429                     nestedScrollWebView.settings.textZoom = 100
3430                 }
3431
3432                 // Set swipe to refresh.
3433                 when (swipeToRefreshInt) {
3434                     SYSTEM_DEFAULT -> {
3435                         // Store the swipe to refresh status in the nested scroll WebView.
3436                         nestedScrollWebView.swipeToRefresh = defaultSwipeToRefresh
3437
3438                         // Update the swipe refresh layout.
3439                         if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
3440                             // 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).
3441                             if (currentWebView != null) {
3442                                 // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
3443                                 swipeRefreshLayout.isEnabled = (currentWebView!!.scrollY == 0)
3444                             }
3445                         } else {  // Swipe to refresh is disabled.
3446                             // Disable the swipe refresh layout.
3447                             swipeRefreshLayout.isEnabled = false
3448                         }
3449                     }
3450
3451                     ENABLED -> {
3452                         // Store the swipe to refresh status in the nested scroll WebView.
3453                         nestedScrollWebView.swipeToRefresh = true
3454
3455                         // 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).
3456                         if (currentWebView != null) {
3457                             // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
3458                             swipeRefreshLayout.isEnabled = (currentWebView!!.scrollY == 0)
3459                         }
3460                     }
3461
3462                     DISABLED -> {
3463                         // Store the swipe to refresh status in the nested scroll WebView.
3464                         nestedScrollWebView.swipeToRefresh = false
3465
3466                         // Disable swipe to refresh.
3467                         swipeRefreshLayout.isEnabled = false
3468                     }
3469                 }
3470
3471                 // Set the WebView theme if algorithmic darkening is supported.
3472                 if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
3473                     // Set the WebView theme.
3474                     when (webViewThemeInt) {
3475                         SYSTEM_DEFAULT ->  // Set the WebView theme.
3476                             when (defaultWebViewTheme) {
3477                                 webViewThemeEntryValuesStringArray[1] ->  // The light theme is selected.  Turn off algorithmic darkening.
3478                                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
3479
3480                                 webViewThemeEntryValuesStringArray[2] ->  // The dark theme is selected.  Turn on algorithmic darkening.
3481                                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
3482
3483                                 else -> {  // The system default theme is selected.
3484                                     // Get the current system theme status.
3485                                     val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
3486
3487                                     // Set the algorithmic darkening according to the current system theme status.
3488                                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)
3489                                 }
3490                             }
3491
3492                         LIGHT_THEME ->  // Turn off algorithmic darkening.
3493                             WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
3494
3495                         DARK_THEME ->  // Turn on algorithmic darkening.
3496                             WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
3497                     }
3498                 }
3499
3500                 // Set the wide viewport status.
3501                 when (wideViewportInt) {
3502                     SYSTEM_DEFAULT -> nestedScrollWebView.settings.useWideViewPort = defaultWideViewport
3503                     ENABLED -> nestedScrollWebView.settings.useWideViewPort = true
3504                     DISABLED -> nestedScrollWebView.settings.useWideViewPort = false
3505                 }
3506
3507                 // Set the display webpage images status.
3508                 when (displayWebpageImagesInt) {
3509                     SYSTEM_DEFAULT -> nestedScrollWebView.settings.loadsImagesAutomatically = defaultDisplayWebpageImages
3510                     ENABLED -> nestedScrollWebView.settings.loadsImagesAutomatically = true
3511                     DISABLED -> nestedScrollWebView.settings.loadsImagesAutomatically = false
3512                 }
3513
3514                 // If there is a pinned SSL certificate, store it in the WebView.
3515                 if (pinnedSslCertificate)
3516                     nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName,
3517                         pinnedSslStartDate, pinnedSslEndDate)
3518
3519                 // If there is a pinned IP address, store it in the WebView.
3520                 if (pinnedIpAddresses)
3521                     nestedScrollWebView.pinnedIpAddresses = pinnedHostIpAddresses
3522
3523                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
3524                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.drawable.domain_settings_url_background)
3525             } else {  // The new URL does not have custom domain settings.  Load the defaults.
3526                 // Store the values from the shared preferences.
3527                 nestedScrollWebView.settings.javaScriptEnabled = defaultJavaScript
3528                 nestedScrollWebView.acceptCookies = defaultCookies
3529                 nestedScrollWebView.settings.domStorageEnabled = defaultDomStorage
3530                 nestedScrollWebView.easyListEnabled = defaultEasyList
3531                 nestedScrollWebView.easyPrivacyEnabled = defaultEasyPrivacy
3532                 nestedScrollWebView.fanboysAnnoyanceListEnabled = defaultFanboysAnnoyanceList
3533                 nestedScrollWebView.fanboysSocialBlockingListEnabled = defaultFanboysSocialBlockingList
3534                 nestedScrollWebView.ultraListEnabled = defaultUltraList
3535                 nestedScrollWebView.ultraPrivacyEnabled = defaultUltraPrivacy
3536                 nestedScrollWebView.blockAllThirdPartyRequests = defaultBlockAllThirdPartyRequests
3537
3538                 // Apply the default cookie setting.
3539                 cookieManager.setAcceptCookie(nestedScrollWebView.acceptCookies)
3540
3541                 // Apply the form data setting if the API < 26.
3542                 if (Build.VERSION.SDK_INT < 26)
3543                     @Suppress("DEPRECATION")
3544                     nestedScrollWebView.settings.saveFormData = defaultFormData
3545
3546                 // Apply the default font size setting.
3547                 try {
3548                     // Try to set the font size from the value in the app settings.
3549                     nestedScrollWebView.settings.textZoom = defaultFontSizeString.toInt()
3550                 } catch (exception: Exception) {
3551                     // If the app settings value is invalid, set the font size to 100%.
3552                     nestedScrollWebView.settings.textZoom = 100
3553                 }
3554
3555                 // Store the swipe to refresh status in the nested scroll WebView.
3556                 nestedScrollWebView.swipeToRefresh = defaultSwipeToRefresh
3557
3558                 // Update the swipe refresh layout.
3559                 if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
3560                     // 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).
3561                     if (currentWebView != null) {
3562                         // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
3563                         swipeRefreshLayout.isEnabled = currentWebView!!.scrollY == 0
3564                     }
3565                 } else {  // Swipe to refresh is disabled.
3566                     // Disable the swipe refresh layout.
3567                     swipeRefreshLayout.isEnabled = false
3568                 }
3569
3570                 // Reset the domain settings database ID.
3571                 nestedScrollWebView.domainSettingsDatabaseId = -1
3572
3573                 // Set the user agent.
3574                 when (val userAgentArrayPosition = userAgentNamesArrayAdapter.getPosition(defaultUserAgentName)) {
3575                     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.
3576                         nestedScrollWebView.settings.userAgentString = defaultUserAgentName
3577
3578                     SETTINGS_WEBVIEW_DEFAULT_USER_AGENT ->  // Set the user agent to `""`, which uses the default value.
3579                         nestedScrollWebView.settings.userAgentString = ""
3580
3581                     SETTINGS_CUSTOM_USER_AGENT ->  // Set the default custom user agent.
3582                         nestedScrollWebView.settings.userAgentString = sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))
3583
3584                     else ->  // Get the user agent string from the user agent data array
3585                         nestedScrollWebView.settings.userAgentString = userAgentDataArray[userAgentArrayPosition]
3586                 }
3587
3588                 // Set the WebView theme if algorithmic darkening is supported.
3589                 if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
3590                     // Set the WebView theme.
3591                     when (defaultWebViewTheme) {
3592                         webViewThemeEntryValuesStringArray[1] ->  // The light theme is selected.  Turn off algorithmic darkening.
3593                             WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
3594
3595                         webViewThemeEntryValuesStringArray[2] ->  // The dark theme is selected.  Turn on algorithmic darkening.
3596                             WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
3597
3598                         else -> {  // The system default theme is selected.  Get the current system theme status.
3599                             // Get the current theme status.
3600                             val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
3601
3602                             // Set the algorithmic darkening according to the current system theme status.
3603                             WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)
3604                         }
3605                     }
3606                 }
3607
3608                 // Set the viewport.
3609                 nestedScrollWebView.settings.useWideViewPort = defaultWideViewport
3610
3611                 // Set the loading of webpage images.
3612                 nestedScrollWebView.settings.loadsImagesAutomatically = defaultDisplayWebpageImages
3613
3614                 // Set a transparent background on the URL relative layout.
3615                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.color.transparent)
3616             }
3617
3618             // Update the privacy icons.
3619             updatePrivacyIcons(true)
3620         }
3621
3622         // Reload the website if returning from the Domains activity.
3623         if (reloadWebsite)
3624             nestedScrollWebView.reload()
3625
3626         // Disable the wide viewport if the source is being viewed.
3627         if (url.startsWith("view-source:"))
3628             nestedScrollWebView.settings.useWideViewPort = false
3629
3630         // 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.
3631         if (loadUrl)
3632             nestedScrollWebView.loadUrl(url)
3633     }
3634
3635     private fun applyProxy(reloadWebViews: Boolean) {
3636         // Set the proxy according to the mode.
3637         proxyHelper.setProxy(applicationContext, appBarLayout, proxyMode)
3638
3639         // Reset the waiting for proxy tracker.
3640         waitingForProxy = false
3641
3642         // Set the proxy.
3643         when (proxyMode) {
3644             ProxyHelper.NONE -> {
3645                 // Initialize a color background typed value.
3646                 val colorBackgroundTypedValue = TypedValue()
3647
3648                 // Get the color background from the theme.
3649                 theme.resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true)
3650
3651                 // Get the color background int from the typed value.
3652                 val colorBackgroundInt = colorBackgroundTypedValue.data
3653
3654                 // Set the default app bar layout background.
3655                 appBarLayout.setBackgroundColor(colorBackgroundInt)
3656             }
3657
3658             ProxyHelper.TOR -> {
3659                 // Set the app bar background to indicate proxying is enabled.
3660                 appBarLayout.setBackgroundResource(R.color.blue_background)
3661
3662                 // Check to see if Orbot is installed.
3663                 try {
3664                     // Get the package manager.
3665                     val packageManager = packageManager
3666
3667                     // 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.
3668                     packageManager.getPackageInfo("org.torproject.android", 0)
3669
3670                     // Check to see if the proxy is ready.
3671                     if (orbotStatus != ProxyHelper.ORBOT_STATUS_ON) {  // Orbot is not ready.
3672                         // Set the waiting for proxy status.
3673                         waitingForProxy = true
3674
3675                         // Show the waiting for proxy dialog if it isn't already displayed.
3676                         if (supportFragmentManager.findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) {
3677                             // Get a handle for the waiting for proxy alert dialog.
3678                             val waitingForProxyDialogFragment = WaitingForProxyDialog()
3679
3680                             // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
3681                             try {
3682                                 // Show the waiting for proxy alert dialog.
3683                                 waitingForProxyDialogFragment.show(supportFragmentManager, getString(R.string.waiting_for_proxy_dialog))
3684                             } catch (waitingForTorException: Exception) {
3685                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
3686                                 pendingDialogsArrayList.add(PendingDialogDataClass(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)))
3687                             }
3688                         }
3689                     }
3690                 } catch (exception: PackageManager.NameNotFoundException) {  // Orbot is not installed.
3691                     // Show the Orbot not installed dialog if it is not already displayed.
3692                     if (supportFragmentManager.findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
3693                         // Get a handle for the Orbot not installed alert dialog.
3694                         val orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode)
3695
3696                         // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
3697                         try {
3698                             // Display the Orbot not installed alert dialog.
3699                             orbotNotInstalledDialogFragment.show(supportFragmentManager, getString(R.string.proxy_not_installed_dialog))
3700                         } catch (orbotNotInstalledException: Exception) {
3701                             // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
3702                             pendingDialogsArrayList.add(PendingDialogDataClass(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)))
3703                         }
3704                     }
3705                 }
3706             }
3707
3708             ProxyHelper.I2P -> {
3709                 // Set the app bar background to indicate proxying is enabled.
3710                 appBarLayout.setBackgroundResource(R.color.blue_background)
3711
3712                 // Check to see if I2P is installed.
3713                 try {
3714                     // 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.
3715                     packageManager.getPackageInfo("net.i2p.android.router", 0)
3716                 } catch (fdroidException: PackageManager.NameNotFoundException) {  // The F-Droid flavor is not installed.
3717                     try {
3718                         // 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.
3719                         packageManager.getPackageInfo("net.i2p.android", 0)
3720                     } catch (googlePlayException: PackageManager.NameNotFoundException) {  // The Google Play flavor is not installed.
3721                         // Sow the I2P not installed dialog if it is not already displayed.
3722                         if (supportFragmentManager.findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
3723                             // Get a handle for the waiting for proxy alert dialog.
3724                             val i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode)
3725
3726                             // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
3727                             try {
3728                                 // Display the I2P not installed alert dialog.
3729                                 i2pNotInstalledDialogFragment.show(supportFragmentManager, getString(R.string.proxy_not_installed_dialog))
3730                             } catch (i2pNotInstalledException: Exception) {
3731                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
3732                                 pendingDialogsArrayList.add(PendingDialogDataClass(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)))
3733                             }
3734                         }
3735                     }
3736                 }
3737             }
3738
3739             ProxyHelper.CUSTOM ->
3740                 // Set the app bar background to indicate proxying is enabled.
3741                 appBarLayout.setBackgroundResource(R.color.blue_background)
3742         }
3743
3744         // Reload the WebViews if requested and not waiting for the proxy.
3745         if (reloadWebViews && !waitingForProxy) {
3746             // Reload the WebViews.
3747             for (i in 0 until webViewStateAdapter!!.itemCount) {
3748                 // Get the WebView tab fragment.
3749                 val webViewTabFragment = webViewStateAdapter!!.getPageFragment(i)
3750
3751                 // Get the fragment view.
3752                 val fragmentView = webViewTabFragment.view
3753
3754                 // Only reload the WebViews if they exist.
3755                 if (fragmentView != null) {
3756                     // Get the nested scroll WebView from the tab fragment.
3757                     val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
3758
3759                     // Reload the WebView.
3760                     nestedScrollWebView.reload()
3761                 }
3762             }
3763         }
3764     }
3765
3766     // The view parameter cannot be removed because it is called from the layout onClick.
3767     fun bookmarksBack(@Suppress("UNUSED_PARAMETER")view: View?) {
3768         if (currentBookmarksFolderId == HOME_FOLDER_ID) {  // The home folder is displayed.
3769             // close the bookmarks drawer.
3770             drawerLayout.closeDrawer(GravityCompat.END)
3771         } else {  // A subfolder is displayed.
3772             // Set the former parent folder as the current folder.
3773             currentBookmarksFolderId = bookmarksDatabaseHelper!!.getParentFolderId(currentBookmarksFolderId)
3774
3775             // Load the new folder.
3776             loadBookmarksFolder()
3777         }
3778     }
3779
3780     private fun calculateSettingsInt(settingCurrentlyEnabled: Boolean, settingEnabledByDefault: Boolean): Int {
3781         return if (settingCurrentlyEnabled == settingEnabledByDefault)  // The current system default is used.
3782             SYSTEM_DEFAULT
3783         else if (settingCurrentlyEnabled)  // The setting is enabled, which is different from the system default.
3784             ENABLED
3785         else  // The setting is disabled, which is different from the system default.
3786             DISABLED
3787     }
3788
3789     private fun clearAndExit() {
3790         // Close the bookmarks cursor if it exists.
3791         bookmarksCursor?.close()
3792
3793         // Close the databases helpers if they exist.
3794         bookmarksDatabaseHelper?.close()
3795         domainsDatabaseHelper?.close()
3796
3797         // Get the status of the clear everything preference.
3798         val clearEverything = sharedPreferences.getBoolean(getString(R.string.clear_everything_key), true)
3799
3800         // Get a handle for the runtime.
3801         val runtime = Runtime.getRuntime()
3802
3803         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
3804         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
3805         val privateDataDirectoryString = applicationInfo.dataDir
3806
3807         // Clear cookies.
3808         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cookies_key), true)) {
3809             // Ass the cookie manager to delete all the cookies.
3810             cookieManager.removeAllCookies(null)
3811
3812             // Ask the cookie manager to flush the cookie database.
3813             cookieManager.flush()
3814
3815             // Manually delete the cookies database, as the cookie manager sometimes will not flush its changes to disk before system exit is run.
3816             try {
3817                 // Two commands must be used because `Runtime.exec()` does not like `*`.
3818                 val deleteCookiesProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/Cookies")
3819                 val deleteCookiesJournalProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/Cookies-journal")
3820
3821                 // Wait until the processes have finished.
3822                 deleteCookiesProcess.waitFor()
3823                 deleteCookiesJournalProcess.waitFor()
3824             } catch (exception: Exception) {
3825                 // Do nothing if an error is thrown.
3826             }
3827         }
3828
3829         // Clear DOM storage.
3830         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_dom_storage_key), true)) {
3831             // Ask web storage to clear the DOM storage.
3832             WebStorage.getInstance().deleteAllData()
3833
3834             // Manually delete the DOM storage files and directories, as web storage sometimes will not flush its changes to disk before system exit is run.
3835             try {
3836                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3837                 val deleteLocalStorageProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Local Storage/"))
3838
3839                 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
3840                 val deleteIndexProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/IndexedDB")
3841                 val deleteQuotaManagerProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager")
3842                 val deleteQuotaManagerJournalProcess = runtime.exec("rm -f $privateDataDirectoryString/app_webview/QuotaManager-journal")
3843                 val deleteDatabaseProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview/databases")
3844
3845                 // Wait until the processes have finished.
3846                 deleteLocalStorageProcess.waitFor()
3847                 deleteIndexProcess.waitFor()
3848                 deleteQuotaManagerProcess.waitFor()
3849                 deleteQuotaManagerJournalProcess.waitFor()
3850                 deleteDatabaseProcess.waitFor()
3851             } catch (exception: Exception) {
3852                 // Do nothing if an error is thrown.
3853             }
3854         }
3855
3856         // Clear form data if the API < 26.
3857         if (Build.VERSION.SDK_INT < 26 && (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_form_data_key), true))) {
3858             // Ask the WebView database to clear the form data.
3859             @Suppress("DEPRECATION")
3860             WebViewDatabase.getInstance(this).clearFormData()
3861
3862             // Manually delete the form data database, as the WebView database sometimes will not flush its changes to disk before system exit is run.
3863             try {
3864                 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
3865                 val deleteWebDataProcess = runtime.exec(arrayOf("rm", "-f", "$privateDataDirectoryString/app_webview/Web Data"))
3866                 val deleteWebDataJournalProcess = runtime.exec(arrayOf("rm", "-f", "$privateDataDirectoryString/app_webview/Web Data-journal"))
3867
3868                 // Wait until the processes have finished.
3869                 deleteWebDataProcess.waitFor()
3870                 deleteWebDataJournalProcess.waitFor()
3871             } catch (exception: Exception) {
3872                 // Do nothing if an error is thrown.
3873             }
3874         }
3875
3876         // Clear the logcat.
3877         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_logcat_key), true)) {
3878             try {
3879                 // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
3880                 val process = Runtime.getRuntime().exec("logcat -b all -c")
3881
3882                 // Wait for the process to finish.
3883                 process.waitFor()
3884             } catch (exception: IOException) {
3885                 // Do nothing.
3886             } catch (exception: InterruptedException) {
3887                 // Do nothing.
3888             }
3889         }
3890
3891         // Clear the cache.
3892         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cache_key), true)) {
3893             // Clear the cache from each WebView.
3894             for (i in 0 until webViewStateAdapter!!.itemCount) {
3895                 // Get the WebView tab fragment.
3896                 val webViewTabFragment = webViewStateAdapter!!.getPageFragment(i)
3897
3898                 // Get the WebView fragment view.
3899                 val webViewFragmentView = webViewTabFragment.view
3900
3901                 // Only clear the cache if the WebView exists.
3902                 if (webViewFragmentView != null) {
3903                     // Get the nested scroll WebView from the tab fragment.
3904                     val nestedScrollWebView = webViewFragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
3905
3906                     // Clear the cache for this WebView.
3907                     nestedScrollWebView.clearCache(true)
3908                 }
3909             }
3910
3911             // Manually delete the cache directories.
3912             try {
3913                 // Delete the main cache directory.
3914                 val deleteCacheProcess = runtime.exec("rm -rf $privateDataDirectoryString/cache")
3915
3916                 // Delete the secondary `Service Worker` cache directory.
3917                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
3918                 val deleteServiceWorkerProcess = runtime.exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Service Worker/"))
3919
3920                 // Wait until the processes have finished.
3921                 deleteCacheProcess.waitFor()
3922                 deleteServiceWorkerProcess.waitFor()
3923             } catch (exception: Exception) {
3924                 // Do nothing if an error is thrown.
3925             }
3926         }
3927
3928         // Wipe out each WebView.
3929         for (i in 0 until webViewStateAdapter!!.itemCount) {
3930             // Get the WebView tab fragment.
3931             val webViewTabFragment = webViewStateAdapter!!.getPageFragment(i)
3932
3933             // Get the WebView frame layout.
3934             val webViewFrameLayout = webViewTabFragment.view as FrameLayout?
3935
3936             // Only wipe out the WebView if it exists.
3937             if (webViewFrameLayout != null) {
3938                 // Get the nested scroll WebView from the tab fragment.
3939                 val nestedScrollWebView = webViewFrameLayout.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
3940
3941                 // Clear SSL certificate preferences for this WebView.
3942                 nestedScrollWebView.clearSslPreferences()
3943
3944                 // Clear the back/forward history for this WebView.
3945                 nestedScrollWebView.clearHistory()
3946
3947                 // Remove all the views from the frame layout.
3948                 webViewFrameLayout.removeAllViews()
3949
3950                 // Destroy the internal state of the WebView.
3951                 nestedScrollWebView.destroy()
3952             }
3953         }
3954
3955         // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
3956         // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
3957         if (clearEverything) {
3958             try {
3959                 // Delete the folder.
3960                 val deleteAppWebviewProcess = runtime.exec("rm -rf $privateDataDirectoryString/app_webview")
3961
3962                 // Wait until the process has finished.
3963                 deleteAppWebviewProcess.waitFor()
3964             } catch (exception: Exception) {
3965                 // Do nothing if an error is thrown.
3966             }
3967         }
3968
3969         // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
3970         finishAndRemoveTask()
3971
3972         // Remove the terminated program from RAM.  The status code is `0`.
3973         exitProcess(0)
3974     }
3975
3976     // The view parameter cannot be removed because it is called from the layout onClick.
3977     fun closeFindOnPage(@Suppress("UNUSED_PARAMETER")view: View?) {
3978         // Delete the contents of the find on page edit text.
3979         findOnPageEditText.text = null
3980
3981         // Clear the highlighted phrases if the WebView is not null.
3982         currentWebView?.clearMatches()
3983
3984         // Hide the find on page linear layout.
3985         findOnPageLinearLayout.visibility = View.GONE
3986
3987         // Show the toolbar.
3988         toolbar.visibility = View.VISIBLE
3989
3990         // Get a handle for the input method manager.
3991         val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
3992
3993         // Hide the keyboard.
3994         inputMethodManager.hideSoftInputFromWindow(toolbar.windowToken, 0)
3995     }
3996
3997     // The view parameter cannot be removed because it is called from the layout onClick.
3998     fun closeTab(@Suppress("UNUSED_PARAMETER")view: View?) {
3999         // Run the command according to the number of tabs.
4000         if (tabLayout.tabCount > 1) {  // There is more than one tab open.
4001             // Get the current tab number.
4002             val currentTabNumber = tabLayout.selectedTabPosition
4003
4004             // Delete the current tab.
4005             tabLayout.removeTabAt(currentTabNumber)
4006
4007             // 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,
4008             // meaning that the current WebView must be reset.  Otherwise it will happen automatically as the selected tab number changes.
4009             if (webViewStateAdapter!!.deletePage(currentTabNumber, webViewViewPager2))
4010                 setCurrentWebView(currentTabNumber)
4011         } else {  // There is only one tab open.
4012             clearAndExit()
4013         }
4014     }
4015
4016     override fun createBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
4017         // Get the dialog.
4018         val dialog = dialogFragment.dialog!!
4019
4020         // Get the views from the dialog fragment.
4021         val createBookmarkNameEditText = dialog.findViewById<EditText>(R.id.create_bookmark_name_edittext)
4022         val createBookmarkUrlEditText = dialog.findViewById<EditText>(R.id.create_bookmark_url_edittext)
4023
4024         // Extract the strings from the edit texts.
4025         val bookmarkNameString = createBookmarkNameEditText.text.toString()
4026         val bookmarkUrlString = createBookmarkUrlEditText.text.toString()
4027
4028         // Create a favorite icon byte array output stream.
4029         val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
4030
4031         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
4032         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
4033
4034         // Convert the favorite icon byte array stream to a byte array.
4035         val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
4036
4037         // Display the new bookmark below the current items in the (0 indexed) list.
4038         val newBookmarkDisplayOrder = bookmarksListView.count
4039
4040         // Create the bookmark.
4041         bookmarksDatabaseHelper!!.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolderId, newBookmarkDisplayOrder, favoriteIconByteArray)
4042
4043         // Update the bookmarks cursor with the current contents of this folder.
4044         bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
4045
4046         // Update the list view.
4047         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
4048
4049         // Scroll to the new bookmark.
4050         bookmarksListView.setSelection(newBookmarkDisplayOrder)
4051     }
4052
4053     override fun createBookmarkFolder(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap) {
4054         // Get the dialog.
4055         val dialog = dialogFragment.dialog!!
4056
4057         // Get handles for the views in the dialog fragment.
4058         val folderNameEditText = dialog.findViewById<EditText>(R.id.folder_name_edittext)
4059         val defaultIconRadioButton = dialog.findViewById<RadioButton>(R.id.default_icon_radiobutton)
4060         val defaultIconImageView = dialog.findViewById<ImageView>(R.id.default_icon_imageview)
4061
4062         // Get new folder name string.
4063         val folderNameString = folderNameEditText.text.toString()
4064
4065         // Set the folder icon bitmap according to the dialog.
4066         val folderIconBitmap: Bitmap = if (defaultIconRadioButton.isChecked) {  // Use the default folder icon.
4067             // Get the default folder icon drawable.
4068             val folderIconDrawable = defaultIconImageView.drawable
4069
4070             // Convert the folder icon drawable to a bitmap drawable.
4071             val folderIconBitmapDrawable = folderIconDrawable as BitmapDrawable
4072
4073             // Convert the folder icon bitmap drawable to a bitmap.
4074             folderIconBitmapDrawable.bitmap
4075         } else {  // Use the WebView favorite icon.
4076             // Copy the favorite icon bitmap to the folder icon bitmap.
4077             favoriteIconBitmap
4078         }
4079
4080         // Create a folder icon byte array output stream.
4081         val folderIconByteArrayOutputStream = ByteArrayOutputStream()
4082
4083         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
4084         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream)
4085
4086         // Convert the folder icon byte array stream to a byte array.
4087         val folderIconByteArray = folderIconByteArrayOutputStream.toByteArray()
4088
4089         // Move all the bookmarks down one in the display order.
4090         for (i in 0 until bookmarksListView.count) {
4091             // Get the bookmark database id.
4092             val databaseId = bookmarksListView.getItemIdAtPosition(i).toInt()
4093
4094             // Move the bookmark down one slot.
4095             bookmarksDatabaseHelper!!.updateDisplayOrder(databaseId, i + 1)
4096         }
4097
4098         // Create the folder, which will be placed at the top of the list view.
4099         bookmarksDatabaseHelper!!.createFolder(folderNameString, currentBookmarksFolderId, folderIconByteArray)
4100
4101         // Update the bookmarks cursor with the current contents of this folder.
4102         bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
4103
4104         // Update the list view.
4105         bookmarksCursorAdapter.changeCursor(bookmarksCursor)
4106
4107         // Scroll to the new folder.
4108         bookmarksListView.setSelection(0)
4109     }
4110
4111     private fun downloadUrlWithExternalApp(url: String) {
4112         // Create a download intent.  Not specifying the action type will display the maximum number of options.
4113         val downloadIntent = Intent()
4114
4115         // Set the URI and the mime type.
4116         downloadIntent.setDataAndType(Uri.parse(url), "text/html")
4117
4118         // Flag the intent to open in a new task.
4119         downloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
4120
4121         // Show the chooser.
4122         startActivity(Intent.createChooser(downloadIntent, getString(R.string.download_with_external_app)))
4123     }
4124
4125     private fun exitFullScreenVideo() {
4126         // Re-enable the screen timeout.
4127         fullScreenVideoFrameLayout.keepScreenOn = false
4128
4129         // Unset the full screen video flag.
4130         displayingFullScreenVideo = false
4131
4132         // Remove all the views from the full screen video frame layout.
4133         fullScreenVideoFrameLayout.removeAllViews()
4134
4135         // Hide the full screen video frame layout.
4136         fullScreenVideoFrameLayout.visibility = View.GONE
4137
4138         // Enable the sliding drawers.
4139         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
4140
4141         // Show the coordinator layout.
4142         coordinatorLayout.visibility = View.VISIBLE
4143
4144         // Apply the appropriate full screen mode flags.
4145         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
4146             // Hide the app bar if specified.
4147             if (hideAppBar) {
4148                 // Hide the tab linear layout.
4149                 tabsLinearLayout.visibility = View.GONE
4150
4151                 // Hide the app bar.
4152                 appBar.hide()
4153             }
4154
4155             /* Hide the system bars.
4156              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4157              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4158              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4159              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4160              */
4161
4162             // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
4163             @Suppress("DEPRECATION")
4164             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
4165         } else {  // Switch to normal viewing mode.
4166             // Remove the `SYSTEM_UI` flags from the root frame layout.  The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
4167             @Suppress("DEPRECATION")
4168             rootFrameLayout.systemUiVisibility = 0
4169         }
4170     }
4171
4172     // The view parameter cannot be removed because it is called from the layout onClick.
4173     fun findNextOnPage(@Suppress("UNUSED_PARAMETER")view: View?) {
4174         // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
4175         currentWebView!!.findNext(true)
4176     }
4177
4178     // The view parameter cannot be removed because it is called from the layout onClick.
4179     fun findPreviousOnPage(@Suppress("UNUSED_PARAMETER")view: View?) {
4180         // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
4181         currentWebView!!.findNext(false)
4182     }
4183
4184     override fun finishedPopulatingFilterLists(combinedFilterLists: ArrayList<ArrayList<List<Array<String>>>>) {
4185         // Store the filter lists.
4186         easyList = combinedFilterLists[0]
4187         easyPrivacy = combinedFilterLists[1]
4188         fanboysAnnoyanceList = combinedFilterLists[2]
4189         fanboysSocialList = combinedFilterLists[3]
4190         ultraList = combinedFilterLists[4]
4191         ultraPrivacy = combinedFilterLists[5]
4192
4193         // Check to see if the activity has been restarted with a saved state.
4194         if ((savedStateArrayList == null) || (savedStateArrayList!!.size == 0)) {  // The activity has not been restarted or it was restarted on start to change the theme.
4195             // Add the first tab.
4196             addNewTab("", false)
4197         } else {  // The activity has been restarted with a saved state.
4198             // Restore each tab.
4199             for (i in savedStateArrayList!!.indices) {
4200                 // Add a new tab.
4201                 tabLayout.addTab(tabLayout.newTab())
4202
4203                 // Get the new tab.
4204                 val newTab = tabLayout.getTabAt(i)!!
4205
4206                 // Set a custom view on the new tab.
4207                 newTab.setCustomView(R.layout.tab_custom_view)
4208
4209                 // Add the new page.
4210                 webViewStateAdapter!!.restorePage(savedStateArrayList!![i], savedNestedScrollWebViewStateArrayList!![i])
4211             }
4212
4213             // Reset the saved state variables.
4214             savedStateArrayList = null
4215             savedNestedScrollWebViewStateArrayList = null
4216
4217             // Get the intent that started the app.
4218             val intent = intent
4219
4220             // Reset the intent.  This prevents a duplicate tab from being created on restart.
4221             setIntent(Intent())
4222
4223             // Get the information from the intent.
4224             val intentAction = intent.action
4225             val intentUriData = intent.data
4226             val intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT)
4227
4228             // Determine if this is a web search.
4229             val isWebSearch = (intentAction != null) && (intentAction == Intent.ACTION_WEB_SEARCH)
4230
4231             // 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.
4232             if ((intentUriData != null) || (intentStringExtra != null) || isWebSearch) {  // A new tab is being loaded.
4233                 // Get the URL string.
4234                 val urlString = if (isWebSearch) {  // The intent is a web search.
4235                     // Sanitize the search input.
4236                     val encodedSearchString: String = try {
4237                         URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8")
4238                     } catch (exception: UnsupportedEncodingException) {
4239                         ""
4240                     }
4241
4242                     // Add the base search URL.
4243                     searchURL + encodedSearchString
4244                 } else { // The intent contains a URL formatted as a URI or a URL in the string extra.
4245                     // Get the URL string.
4246                     intentUriData?.toString() ?: intentStringExtra!!
4247                 }
4248
4249                 // Add a new tab if specified in the preferences.
4250                 if (sharedPreferences.getBoolean(getString(R.string.open_intents_in_new_tab_key), true)) {  // Load the URL in a new tab.
4251                     // Set the loading new intent flag.
4252                     loadingNewIntent = true
4253
4254                     // Add a new tab.
4255                     addNewTab(urlString, true)
4256                 } else {  // Load the URL in the current tab.
4257                     // Make it so.
4258                     loadUrl(currentWebView!!, urlString)
4259                 }
4260             } else {  // A new tab is not being loaded.
4261                 // Restore the selected tab position.
4262                 if (savedTabPosition == 0) {  // The first tab is selected.
4263                     // Set the first page as the current WebView.
4264                     setCurrentWebView(0)
4265                 } else {  // The first tab is not selected.
4266                     // Select the tab when the layout has finished populating.
4267                     tabLayout.post {
4268                         // Get a handle for the tab.
4269                         val tab = tabLayout.getTabAt(savedTabPosition)!!
4270
4271                         // Select the tab.
4272                         tab.select()
4273                     }
4274                 }
4275             }
4276         }
4277     }
4278
4279     // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
4280     @SuppressLint("ClickableViewAccessibility")
4281     private fun initializeApp() {
4282         // Get a handle for the input method.
4283         val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
4284
4285         // Initialize the color spans for highlighting the URLs.
4286         initialGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500))
4287         finalGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500))
4288         redColorSpan = ForegroundColorSpan(getColor(R.color.red_text))
4289
4290         // Remove the formatting from the URL edit text when the user is editing the text.
4291         urlEditText.onFocusChangeListener = View.OnFocusChangeListener { _: View?, hasFocus: Boolean ->
4292             if (hasFocus) {  // The user is editing the URL text box.
4293                 // Remove the syntax highlighting.
4294                 urlEditText.text.removeSpan(redColorSpan)
4295                 urlEditText.text.removeSpan(initialGrayColorSpan)
4296                 urlEditText.text.removeSpan(finalGrayColorSpan)
4297             } else {  // The user has stopped editing the URL text box.
4298                 // Move to the beginning of the string.
4299                 urlEditText.setSelection(0)
4300
4301                 // Reapply the syntax highlighting.
4302                 UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
4303             }
4304         }
4305
4306         // Set the go button on the keyboard to load the URL in url text box.
4307         urlEditText.setOnKeyListener { _: View?, keyCode: Int, keyEvent: KeyEvent ->
4308             // If the event is a key-down event on the `enter` button, load the URL.
4309             if ((keyEvent.action == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The enter key was pressed.
4310                 // Load the URL.
4311                 loadUrlFromTextBox()
4312
4313                 // Consume the event.
4314                 return@setOnKeyListener true
4315             } else {  // Some other key was pressed.
4316                 // Do not consume the event.
4317                 return@setOnKeyListener false
4318             }
4319         }
4320
4321         // Create an Orbot status broadcast receiver.
4322         orbotStatusBroadcastReceiver = object : BroadcastReceiver() {
4323             override fun onReceive(context: Context, intent: Intent) {
4324                 // Get the content of the status message.
4325                 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS")!!
4326
4327                 // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
4328                 if ((orbotStatus == ProxyHelper.ORBOT_STATUS_ON) && waitingForProxy) {
4329                     // Reset the waiting for proxy status.
4330                     waitingForProxy = false
4331
4332                     // Get a list of the current fragments.
4333                     val fragmentList = supportFragmentManager.fragments
4334
4335                     // Check each fragment to see if it is a waiting for proxy dialog.  Sometimes more than one is displayed.
4336                     for (i in fragmentList.indices) {
4337                         // Get the fragment tag.
4338                         val fragmentTag = fragmentList[i].tag
4339
4340                         // Check to see if it is the waiting for proxy dialog.
4341                         if (fragmentTag != null && fragmentTag == getString(R.string.waiting_for_proxy_dialog)) {
4342                             // Dismiss the waiting for proxy dialog.
4343                             (fragmentList[i] as DialogFragment).dismiss()
4344                         }
4345                     }
4346
4347                     // Reload existing URLs and load any URLs that are waiting for the proxy.
4348                     for (i in 0 until webViewStateAdapter!!.itemCount) {
4349                         // Get the WebView tab fragment.
4350                         val webViewTabFragment = webViewStateAdapter!!.getPageFragment(i)
4351
4352                         // Get the fragment view.
4353                         val fragmentView = webViewTabFragment.view
4354
4355                         // Only process the WebViews if they exist.
4356                         if (fragmentView != null) {
4357                             // Get the nested scroll WebView from the tab fragment.
4358                             val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)
4359
4360                             // Get the waiting for proxy URL string.
4361                             val waitingForProxyUrlString = nestedScrollWebView.waitingForProxyUrlString
4362
4363                             // Load the pending URL if it exists.
4364                             if (waitingForProxyUrlString.isNotEmpty()) {  // A URL is waiting to be loaded.
4365                                 // Load the URL.
4366                                 loadUrl(nestedScrollWebView, waitingForProxyUrlString)
4367
4368                                 // Reset the waiting for proxy URL string.
4369                                 nestedScrollWebView.waitingForProxyUrlString = ""
4370                             } else {  // No URL is waiting to be loaded.
4371                                 // Reload the existing URL.
4372                                 nestedScrollWebView.reload()
4373                             }
4374                         }
4375                     }
4376                 }
4377             }
4378         }
4379
4380         // Register the Orbot status broadcast receiver.  `ContextCompat` must be used until the minimum API >= 34.
4381         ContextCompat.registerReceiver(this, orbotStatusBroadcastReceiver, IntentFilter("org.torproject.android.intent.action.STATUS"), ContextCompat.RECEIVER_EXPORTED)
4382
4383         // Get handles for views that need to be modified.
4384         val bookmarksHeaderLinearLayout = findViewById<LinearLayout>(R.id.bookmarks_header_linearlayout)
4385         val launchBookmarksActivityFab = findViewById<FloatingActionButton>(R.id.launch_bookmarks_activity_fab)
4386         val createBookmarkFolderFab = findViewById<FloatingActionButton>(R.id.create_bookmark_folder_fab)
4387         val createBookmarkFab = findViewById<FloatingActionButton>(R.id.create_bookmark_fab)
4388
4389         // Handle tab selections.
4390         tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
4391             override fun onTabSelected(tab: TabLayout.Tab) {
4392                 // Close the find on page bar if it is open.
4393                 closeFindOnPage(null)
4394
4395                 // 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.
4396                 webViewViewPager2.post {
4397                     // Select the same page in the view pager.
4398                     webViewViewPager2.currentItem = tab.position
4399
4400                     // Set the current WebView.
4401                     setCurrentWebView(tab.position)
4402                 }
4403             }
4404
4405             override fun onTabUnselected(tab: TabLayout.Tab) {}
4406
4407             override fun onTabReselected(tab: TabLayout.Tab) {
4408                 // Only display the view SSL certificate dialog if the current WebView is not null.
4409                 // This can happen if the tab is programmatically reselected while the app is being restarted and is not yet populated.
4410                 if (currentWebView != null) {
4411                     // Instantiate the View SSL Certificate dialog.
4412                     val viewSslCertificateDialogFragment: DialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView!!.webViewFragmentId, currentWebView!!.getFavoriteIcon())
4413
4414                     // Display the View SSL Certificate dialog.
4415                     viewSslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.view_ssl_certificate))
4416                 }
4417             }
4418         })
4419
4420         // Set a touch listener on the bookmarks header linear layout so that touches don't pass through to the button underneath.
4421         bookmarksHeaderLinearLayout.setOnTouchListener { _: View?, _: MotionEvent? -> true }
4422
4423         // Set the launch bookmarks activity floating action button to launch the bookmarks activity.
4424         launchBookmarksActivityFab.setOnClickListener {
4425             // Get a copy of the favorite icon bitmap.
4426             val currentFavoriteIconBitmap = currentWebView!!.getFavoriteIcon()
4427
4428             // Create a favorite icon byte array output stream.
4429             val currentFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
4430
4431             // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
4432             currentFavoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, currentFavoriteIconByteArrayOutputStream)
4433
4434             // Convert the favorite icon byte array stream to a byte array.
4435             val currentFavoriteIconByteArray = currentFavoriteIconByteArrayOutputStream.toByteArray()
4436
4437             // Create an intent to launch the bookmarks activity.
4438             val bookmarksIntent = Intent(applicationContext, BookmarksActivity::class.java)
4439
4440             // Add the extra information to the intent.
4441             bookmarksIntent.putExtra(CURRENT_FOLDER_ID, currentBookmarksFolderId)
4442             bookmarksIntent.putExtra(CURRENT_TITLE, currentWebView!!.title)
4443             bookmarksIntent.putExtra(CURRENT_URL, currentWebView!!.url)
4444             bookmarksIntent.putExtra(CURRENT_FAVORITE_ICON_BYTE_ARRAY, currentFavoriteIconByteArray)
4445
4446             // Make it so.
4447             startActivity(bookmarksIntent)
4448         }
4449
4450         // Set the create new bookmark folder floating action button to display an alert dialog.
4451         createBookmarkFolderFab.setOnClickListener {
4452             // Create a create bookmark folder dialog.
4453             val createBookmarkFolderDialog: DialogFragment = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView!!.getFavoriteIcon())
4454
4455             // Show the create bookmark folder dialog.
4456             createBookmarkFolderDialog.show(supportFragmentManager, getString(R.string.create_folder))
4457         }
4458
4459         // Set the create new bookmark floating action button to display an alert dialog.
4460         createBookmarkFab.setOnClickListener {
4461             // Instantiate the create bookmark dialog.
4462             val createBookmarkDialog: DialogFragment = CreateBookmarkDialog.createBookmark(currentWebView!!.url!!, currentWebView!!.title!!, currentWebView!!.getFavoriteIcon())
4463
4464             // Display the create bookmark dialog.
4465             createBookmarkDialog.show(supportFragmentManager, getString(R.string.create_bookmark))
4466         }
4467
4468         // Search for the string on the page whenever a character changes in the find on page edit text.
4469         findOnPageEditText.addTextChangedListener(object : TextWatcher {
4470             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
4471
4472             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
4473
4474             override fun afterTextChanged(s: Editable) {
4475                 // 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.
4476                 currentWebView?.findAllAsync(findOnPageEditText.text.toString())
4477             }
4478         })
4479
4480         // Set the `check mark` button for the find on page edit text keyboard to close the soft keyboard.
4481         findOnPageEditText.setOnKeyListener { _: View?, keyCode: Int, keyEvent: KeyEvent ->
4482             if ((keyEvent.action == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
4483                 // Hide the soft keyboard.
4484                 inputMethodManager.hideSoftInputFromWindow(currentWebView!!.windowToken, 0)
4485
4486                 // Consume the event.
4487                 return@setOnKeyListener true
4488             } else {  // A different key was pressed.
4489                 // Do not consume the event.
4490                 return@setOnKeyListener false
4491             }
4492         }
4493
4494         // Implement swipe to refresh.
4495         swipeRefreshLayout.setOnRefreshListener {
4496             // Reload the website.
4497             currentWebView!!.reload()
4498         }
4499
4500         // Store the default progress view offsets.
4501         defaultProgressViewStartOffset = swipeRefreshLayout.progressViewStartOffset
4502         defaultProgressViewEndOffset = swipeRefreshLayout.progressViewEndOffset
4503
4504         // Set the refresh color scheme according to the theme.
4505         swipeRefreshLayout.setColorSchemeResources(R.color.blue_text)
4506
4507         // Initialize a color background typed value.
4508         val colorBackgroundTypedValue = TypedValue()
4509
4510         // Get the color background from the theme.
4511         theme.resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true)
4512
4513         // Get the color background int from the typed value.
4514         val colorBackgroundInt = colorBackgroundTypedValue.data
4515
4516         // Set the swipe refresh background color.
4517         swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt)
4518
4519         // Set the drawer titles, which identify the drawer layouts in accessibility mode.
4520         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer))
4521         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks))
4522
4523         // Load the bookmarks folder.
4524         loadBookmarksFolder()
4525
4526         // Handle clicks on bookmarks.
4527         bookmarksListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
4528             // Convert the id from long to int to match the format of the bookmarks database.
4529             val databaseId = id.toInt()
4530
4531             // Get the bookmark cursor for this ID.
4532             val bookmarkCursor = bookmarksDatabaseHelper!!.getBookmark(databaseId)
4533
4534             // Move the bookmark cursor to the first row.
4535             bookmarkCursor.moveToFirst()
4536
4537             // Act upon the bookmark according to the type.
4538             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
4539                 // Store the folder ID.
4540                 currentBookmarksFolderId = bookmarkCursor.getLong(bookmarkCursor.getColumnIndexOrThrow(FOLDER_ID))
4541
4542                 // Load the new folder.
4543                 loadBookmarksFolder()
4544             } else {  // The selected bookmark is not a folder.
4545                 // Load the bookmark URL.
4546                 loadUrl(currentWebView!!, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_URL)))
4547
4548                 // Close the bookmarks drawer if it is not pinned.
4549                 if (!bookmarksDrawerPinned)
4550                     drawerLayout.closeDrawer(GravityCompat.END)
4551             }
4552
4553             // Close the cursor.
4554             bookmarkCursor.close()
4555         }
4556
4557         // Handle long-presses on bookmarks.
4558         bookmarksListView.onItemLongClickListener = AdapterView.OnItemLongClickListener { _: AdapterView<*>?, _: View?, _: Int, id: Long ->
4559             // Convert the database ID from `long` to `int`.
4560             val databaseId = id.toInt()
4561
4562             // Run the commands associated with the type.
4563             if (bookmarksDatabaseHelper!!.isFolder(databaseId)) {  // The bookmark is a folder.
4564                 // Get the folder ID.
4565                 val folderId = bookmarksDatabaseHelper!!.getFolderId(databaseId)
4566
4567                 // Get a cursor of all the bookmarks in the folder.
4568                 val bookmarksCursor = bookmarksDatabaseHelper!!.getFolderBookmarks(folderId)
4569
4570                 // Move to the first entry in the cursor.
4571                 bookmarksCursor.moveToFirst()
4572
4573                 // Open each bookmark
4574                 for (i in 0 until bookmarksCursor.count) {
4575                     // Load the bookmark in a new tab, moving to the tab for the first bookmark if the drawer is not pinned.
4576                     addNewTab(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BOOKMARK_URL)), !bookmarksDrawerPinned && (i == 0))
4577
4578                     // Move to the next bookmark.
4579                     bookmarksCursor.moveToNext()
4580                 }
4581
4582                 // Close the cursor.
4583                 bookmarksCursor.close()
4584             } else {  // The bookmark is not a folder.
4585                 // Get the bookmark cursor for this ID.
4586                 val bookmarkCursor = bookmarksDatabaseHelper!!.getBookmark(databaseId)
4587
4588                 // Move the bookmark cursor to the first row.
4589                 bookmarkCursor.moveToFirst()
4590
4591                 // Load the bookmark in a new tab and move to the tab if the drawer is not pinned.
4592                 addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BOOKMARK_URL)), !bookmarksDrawerPinned)
4593
4594                 // Close the cursor.
4595                 bookmarkCursor.close()
4596             }
4597
4598             // Close the bookmarks drawer if it is not pinned.
4599             if (!bookmarksDrawerPinned)
4600                 drawerLayout.closeDrawer(GravityCompat.END)
4601
4602             // Consume the event.
4603             true
4604         }
4605
4606         // The drawer listener is used to update the navigation menu.
4607         drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
4608             override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
4609
4610             override fun onDrawerOpened(drawerView: View) {}
4611
4612             override fun onDrawerClosed(drawerView: View) {
4613                 // Reset the drawer icon when the drawer is closed.  Otherwise, it remains an arrow if the drawer is open when the app is restarted.
4614                 actionBarDrawerToggle!!.syncState()
4615             }
4616
4617             override fun onDrawerStateChanged(newState: Int) {
4618                 if (newState == DrawerLayout.STATE_SETTLING || newState == DrawerLayout.STATE_DRAGGING) {  // A drawer is opening or closing.
4619                     // Update the navigation menu items if the WebView is not null.
4620                     if (currentWebView != null) {
4621                         // Set the enabled status of the menu items.
4622                         navigationBackMenuItem.isEnabled = currentWebView!!.canGoBack()
4623                         navigationForwardMenuItem.isEnabled = currentWebView!!.canGoForward()
4624                         navigationScrollToBottomMenuItem.isEnabled = (currentWebView!!.canScrollVertically(-1) || currentWebView!!.canScrollVertically(1))
4625                         navigationHistoryMenuItem.isEnabled = currentWebView!!.canGoBack() || currentWebView!!.canGoForward()
4626
4627                         // Update the scroll menu item.
4628                         if (currentWebView!!.scrollY == 0) {  // The WebView is scrolled to the top.
4629                             // Set the title.
4630                             navigationScrollToBottomMenuItem.title = getString(R.string.scroll_to_bottom)
4631
4632                             // Set the icon.
4633                             navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_down_enabled)
4634                         } else {  // The WebView is not scrolled to the top.
4635                             // Set the title.
4636                             navigationScrollToBottomMenuItem.title = getString(R.string.scroll_to_top)
4637
4638                             // Set the icon.
4639                             navigationScrollToBottomMenuItem.icon = AppCompatResources.getDrawable(applicationContext, R.drawable.move_up_enabled)
4640                         }
4641
4642                         // Display the number of blocked requests.
4643                         navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + currentWebView!!.getRequestsCount(BLOCKED_REQUESTS)
4644
4645                         // Hide the keyboard (if displayed).
4646                         inputMethodManager.hideSoftInputFromWindow(currentWebView!!.windowToken, 0)
4647                     }
4648
4649                     // 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.
4650                     urlEditText.clearFocus()
4651
4652                     // 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.
4653                     // Clearing the focus from the WebView removes any text selection markers and context menus, which otherwise draw above the open drawers.
4654                     currentWebView?.clearFocus()
4655
4656                     if (bottomAppBar && navigationDrawerFirstView) {
4657                         // Reset the navigation drawer first view flag.
4658                         navigationDrawerFirstView = false
4659
4660                         // Get a handle for the navigation recycler view.
4661                         val navigationRecyclerView = navigationView.getChildAt(0) as RecyclerView
4662
4663                         // Get the navigation linear layout manager.
4664                         val navigationLinearLayoutManager = navigationRecyclerView.layoutManager as LinearLayoutManager
4665
4666                         // Scroll the navigation drawer to the bottom.
4667                         navigationLinearLayoutManager.scrollToPositionWithOffset(13, 0)
4668                     }
4669                 }
4670             }
4671         })
4672
4673         // Inflate a bare WebView to get the default user agent.  It is not used to render content on the screen.
4674         @SuppressLint("InflateParams") val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, null, false)
4675
4676         // Get a handle for the WebView.
4677         val bareWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
4678
4679         // Store the default user agent.
4680         webViewDefaultUserAgent = bareWebView.settings.userAgentString
4681
4682         // Destroy the bare WebView.
4683         bareWebView.destroy()
4684
4685         // Update the domains settings set.
4686         updateDomainsSettingsSet()
4687
4688         // Instantiate the check filter list helper.
4689         checkFilterListHelper = CheckFilterListHelper()
4690     }
4691
4692     @SuppressLint("ClickableViewAccessibility")
4693     override fun initializeWebView(nestedScrollWebView: NestedScrollWebView, pageNumber: Int, progressBar: ProgressBar, urlString: String, restoringState: Boolean) {
4694         // Get the WebView theme.
4695         val webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value))
4696
4697         // Get the WebView theme entry values string array.
4698         val webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values)
4699
4700         // Set the WebView theme if algorithmic darkening is supported.
4701         if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
4702             // Set the WebView them.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
4703             if (webViewTheme == webViewThemeEntryValuesStringArray[1]) {  // The light theme is selected.
4704                 // Turn off algorithmic darkening.
4705                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
4706
4707                 // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
4708                 // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
4709                 nestedScrollWebView.visibility = View.VISIBLE
4710             } else if (webViewTheme == webViewThemeEntryValuesStringArray[2]) {  // The dark theme is selected.
4711                 // Turn on algorithmic darkening.
4712                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
4713             } else {  // The system default theme is selected.
4714                 // Get the current theme status.
4715                 val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
4716
4717                 // Set the algorithmic darkening according to the current system theme status.
4718                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
4719                     // Turn off algorithmic darkening.
4720                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, false)
4721
4722                     // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
4723                     // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
4724                     nestedScrollWebView.visibility = View.VISIBLE
4725                 } else {  // The system is in night mode.
4726                     // Turn on algorithmic darkening.
4727                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.settings, true)
4728                 }
4729             }
4730         }
4731
4732         // Get a handle for the input method manager.
4733         val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
4734
4735         // Set the app bar scrolling.
4736         nestedScrollWebView.isNestedScrollingEnabled = scrollAppBar
4737
4738         // Allow pinch to zoom.
4739         nestedScrollWebView.settings.builtInZoomControls = true
4740
4741         // Hide zoom controls.
4742         nestedScrollWebView.settings.displayZoomControls = false
4743
4744         // Don't allow mixed content (HTTP and HTTPS) on the same website.
4745         nestedScrollWebView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
4746
4747         // Set the WebView to load in overview mode (zoomed out to the maximum width).
4748         nestedScrollWebView.settings.loadWithOverviewMode = true
4749
4750         // Explicitly disable geolocation.
4751         nestedScrollWebView.settings.setGeolocationEnabled(false)
4752
4753         // Allow loading of file:// URLs.  This is necessary for opening MHT web archives, which are copied into a temporary cache location.
4754         nestedScrollWebView.settings.allowFileAccess = true
4755
4756         // Create a double-tap gesture detector to toggle full-screen mode.
4757         val doubleTapGestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
4758             // Override `onDoubleTap()`.  All other events are handled using the default settings.
4759             override fun onDoubleTap(motionEvent: MotionEvent): Boolean {
4760                 return if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
4761                     // Toggle the full screen browsing mode tracker.
4762                     inFullScreenBrowsingMode = !inFullScreenBrowsingMode
4763
4764                     // Toggle the full screen browsing mode.
4765                     if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
4766                         // Hide the app bar if specified.
4767                         if (hideAppBar) {  // App bar hiding is enabled.
4768                             // Close the find on page bar if it is visible.
4769                             closeFindOnPage(null)
4770
4771                             // Hide the tab linear layout.
4772                             tabsLinearLayout.visibility = View.GONE
4773
4774                             // Hide the app bar.
4775                             appBar.hide()
4776
4777                             // Set layout and scrolling parameters according to the position of the app bar.
4778                             if (bottomAppBar) {  // The app bar is at the bottom.
4779                                 // Reset the WebView padding to fill the available space.
4780                                 swipeRefreshLayout.setPadding(0, 0, 0, 0)
4781                             } else {  // The app bar is at the top.
4782                                 // Check to see if the app bar is normally scrolled.
4783                                 if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
4784                                     // Get the swipe refresh layout parameters.
4785                                     val swipeRefreshLayoutParams = swipeRefreshLayout.layoutParams as CoordinatorLayout.LayoutParams
4786
4787                                     // Remove the off-screen scrolling layout.
4788                                     swipeRefreshLayoutParams.behavior = null
4789                                 } else {  // The app bar is not scrolled when it is displayed.
4790                                     // Remove the padding from the top of the swipe refresh layout.
4791                                     swipeRefreshLayout.setPadding(0, 0, 0, 0)
4792
4793                                     // The swipe refresh circle must be moved above the now removed status bar location.
4794                                     swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset)
4795                                 }
4796                             }
4797                         } else {  // App bar hiding is not enabled.
4798                             // Adjust the UI for the bottom app bar.
4799                             if (bottomAppBar) {
4800                                 // Adjust the UI according to the scrolling of the app bar.
4801                                 if (scrollAppBar) {
4802                                     // Reset the WebView padding to fill the available space.
4803                                     swipeRefreshLayout.setPadding(0, 0, 0, 0)
4804                                 } else {
4805                                     // Move the WebView above the app bar layout.
4806                                     swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight)
4807                                 }
4808                             }
4809                         }
4810
4811                         /* Hide the system bars.
4812                          * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4813                          * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4814                          * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4815                          * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4816                          */
4817
4818                         // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
4819                         @Suppress("DEPRECATION")
4820                         rootFrameLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
4821                                 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
4822                     } else {  // Switch to normal viewing mode.
4823                         // Show the app bar if it was hidden.
4824                         if (hideAppBar) {
4825                             // Show the tab linear layout.
4826                             tabsLinearLayout.visibility = View.VISIBLE
4827
4828                             // Show the app bar.
4829                             appBar.show()
4830                         }
4831
4832                         // Set layout and scrolling parameters according to the position of the app bar.
4833                         if (bottomAppBar) {  // The app bar is at the bottom.
4834                             // Adjust the UI.
4835                             if (scrollAppBar) {
4836                                 // Reset the WebView padding to fill the available space.
4837                                 swipeRefreshLayout.setPadding(0, 0, 0, 0)
4838                             } else {
4839                                 // Move the WebView above the app bar layout.
4840                                 swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight)
4841                             }
4842                         } else {  // The app bar is at the top.
4843                             // Check to see if the app bar is normally scrolled.
4844                             if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
4845                                 // Get the swipe refresh layout parameters.
4846                                 val swipeRefreshLayoutParams = swipeRefreshLayout.layoutParams as CoordinatorLayout.LayoutParams
4847
4848                                 // Add the off-screen scrolling layout.
4849                                 swipeRefreshLayoutParams.behavior = AppBarLayout.ScrollingViewBehavior()
4850                             } else {  // The app bar is not scrolled when it is displayed.
4851                                 // The swipe refresh layout must be manually moved below the app bar layout.
4852                                 swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0)
4853
4854                                 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
4855                                 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight)
4856                             }
4857                         }
4858
4859                         // Remove the `SYSTEM_UI` flags from the root frame layout.  The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
4860                         @Suppress("DEPRECATION")
4861                         rootFrameLayout.systemUiVisibility = 0
4862                     }
4863
4864                     // Consume the double-tap.
4865                     true
4866                 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
4867                     // Return false.
4868                     false
4869                 }
4870             }
4871
4872             override fun onFling(motionEvent1: MotionEvent?, motionEvent2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
4873                 // Scroll the bottom app bar if enabled.
4874                 if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning && (motionEvent1 != null)) {
4875                     // Calculate the Y change.
4876                     val motionY = motionEvent2.y - motionEvent1.y
4877
4878                     // Scroll the app bar if the change is greater than 50 pixels.
4879                     if (motionY > 50) {
4880                         // Animate the bottom app bar onto the screen.
4881                         objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0f)
4882                     } else if (motionY < -50) {
4883                         // Animate the bottom app bar off the screen.
4884                         objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.height.toFloat())
4885                     }
4886
4887                     // Make it so.
4888                     objectAnimator.start()
4889                 }
4890
4891                 // Do not consume the event.
4892                 return false
4893             }
4894         })
4895
4896         // Pass all touch events on the WebView through the double-tap gesture detector.
4897         nestedScrollWebView.setOnTouchListener { view: View, motionEvent: MotionEvent? ->
4898             // Call `performClick()` on the view, which is required for accessibility.
4899             view.performClick()
4900
4901             // Check for double-taps.
4902             doubleTapGestureDetector.onTouchEvent(motionEvent!!)
4903         }
4904
4905         // Register the WebView for a context menu.  This is used to see link targets and download images.
4906         registerForContextMenu(nestedScrollWebView)
4907
4908         // Allow the downloading of files.
4909         nestedScrollWebView.setDownloadListener { downloadUrlString: String?, userAgent: String?, contentDisposition: String?, mimetype: String?, contentLength: Long ->
4910             // Check the download preference.
4911             if (downloadWithExternalApp) {  // Download with an external app.
4912                 downloadUrlWithExternalApp(downloadUrlString!!)
4913             } else {  // Handle the download inside of Privacy Browser.
4914                 // Define a formatted file size string.
4915
4916                 // Process the content length if it contains data.
4917                 val formattedFileSizeString = if (contentLength > 0) {  // The content length is greater than 0.
4918                     // Format the content length as a string.
4919                     NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes)
4920                 } else {  // The content length is not greater than 0.
4921                     // Set the formatted file size string to be `unknown size`.
4922                     getString(R.string.unknown_size)
4923                 }
4924
4925                 // Get the file name from the content disposition.
4926                 val fileNameString = UrlHelper.getFileName(this, contentDisposition, mimetype, downloadUrlString!!)
4927
4928                 // Instantiate the save dialog.
4929                 val saveDialogFragment = SaveDialog.saveUrl(downloadUrlString, fileNameString, formattedFileSizeString, userAgent!!, nestedScrollWebView.acceptCookies)
4930
4931                 // 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.
4932                 try {
4933                     // Show the save dialog.
4934                     saveDialogFragment.show(supportFragmentManager, getString(R.string.save_dialog))
4935                 } catch (exception: Exception) {  // The dialog could not be shown.
4936                     // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
4937                     pendingDialogsArrayList.add(PendingDialogDataClass(saveDialogFragment, getString(R.string.save_dialog)))
4938                 }
4939             }
4940
4941             // Get the current page position.
4942             val currentPagePosition = webViewStateAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
4943
4944             // Get the corresponding tab.
4945             val tab = tabLayout.getTabAt(currentPagePosition)!!
4946
4947             // Get the tab custom view.
4948             val tabCustomView = tab.customView!!
4949
4950             // Get the tab views.
4951             val tabFavoriteIconImageView = tabCustomView.findViewById<ImageView>(R.id.favorite_icon_imageview)
4952             val tabTitleTextView = tabCustomView.findViewById<TextView>(R.id.title_textview)
4953
4954             // Restore the previous webpage favorite icon and title if the title is currently set to `Loading...`.
4955             if (tabTitleTextView.text.toString() == getString(R.string.loading)) {
4956                 // Restore the previous webpage title text.
4957                 tabTitleTextView.text = nestedScrollWebView.previousWebpageTitle
4958
4959                 // Restore the previous webpage favorite icon if it is not null.
4960                 if (nestedScrollWebView.previousFavoriteIconDrawable != null)
4961                     tabFavoriteIconImageView.setImageDrawable(nestedScrollWebView.previousFavoriteIconDrawable)
4962             }
4963         }
4964
4965         // Update the find on page count.
4966         nestedScrollWebView.setFindListener { activeMatchOrdinal, numberOfMatches, isDoneCounting ->
4967             if (isDoneCounting && (numberOfMatches == 0)) {  // There are no matches.
4968                 // Set the find on page count text view to be `0/0`.
4969                 findOnPageCountTextView.setText(R.string.zero_of_zero)
4970             } else if (isDoneCounting) {  // There are matches.
4971                 // The active match ordinal is zero-based.
4972                 val activeMatch = activeMatchOrdinal + 1
4973
4974                 // Build the match string.
4975                 val matchString = "$activeMatch/$numberOfMatches"
4976
4977                 // Update the find on page count text view.
4978                 findOnPageCountTextView.text = matchString
4979             }
4980         }
4981
4982         // Process scroll changes.
4983         nestedScrollWebView.setOnScrollChangeListener { _: View?, _: Int, _: Int, _: Int, _: Int ->
4984             // Set the swipe to refresh status.
4985             if (nestedScrollWebView.swipeToRefresh)  // Only enable swipe to refresh if the WebView is scrolled to the top.
4986                 swipeRefreshLayout.isEnabled = nestedScrollWebView.scrollY == 0
4987             else  // Disable swipe to refresh.
4988                 swipeRefreshLayout.isEnabled = false
4989
4990             // Reinforce the system UI visibility flags if in full screen browsing mode.
4991             // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
4992             if (inFullScreenBrowsingMode) {
4993                 /* Hide the system bars.
4994                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4995                  * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4996                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4997                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4998                  */
4999
5000                 // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
5001                 @Suppress("DEPRECATION")
5002                 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
5003             }
5004         }
5005
5006         // Set the web chrome client.
5007         nestedScrollWebView.webChromeClient = object : WebChromeClient() {
5008             // Update the progress bar when a page is loading.
5009             override fun onProgressChanged(view: WebView, progress: Int) {
5010                 // Update the progress bar.
5011                 progressBar.progress = progress
5012
5013                 // Set the visibility of the progress bar.
5014                 if (progress < 100) {
5015                     // Show the progress bar.
5016                     progressBar.visibility = View.VISIBLE
5017                 } else {
5018                     // Hide the progress bar.
5019                     progressBar.visibility = View.GONE
5020
5021                     //Stop the swipe to refresh indicator if it is running
5022                     swipeRefreshLayout.isRefreshing = false
5023
5024                     // 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.
5025                     nestedScrollWebView.visibility = View.VISIBLE
5026                 }
5027             }
5028
5029             // Set the favorite icon when it changes.
5030             override fun onReceivedIcon(view: WebView, icon: Bitmap) {
5031                 // 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.
5032                 // This prevents low resolution icons from replacing high resolution one.
5033                 // The check for the visibility of the progress bar can possibly be removed once https://redmine.stoutner.com/issues/747 is fixed.
5034                 if ((progressBar.visibility == View.GONE) && (icon.height > nestedScrollWebView.getFavoriteIconHeight())) {
5035                     // Store the new favorite icon.
5036                     nestedScrollWebView.setFavoriteIcon(icon)
5037
5038                     // Get the current page position.
5039                     val currentPosition = webViewStateAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
5040
5041                     // Get the current tab.
5042                     val tab = tabLayout.getTabAt(currentPosition)
5043
5044                     // Check to see if the tab has been populated.
5045                     if (tab != null) {
5046                         // Get the custom view from the tab.
5047                         val tabView = tab.customView
5048
5049                         // Check to see if the custom tab view has been populated.
5050                         if (tabView != null) {
5051                             // Get the favorite icon image view from the tab.
5052                             val tabFavoriteIconImageView = tabView.findViewById<ImageView>(R.id.favorite_icon_imageview)
5053
5054                             // Display the favorite icon in the tab.
5055                             tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true))
5056                         }
5057                     }
5058                 }
5059             }
5060
5061             // Save a copy of the title when it changes.
5062             override fun onReceivedTitle(view: WebView, title: String) {
5063                 // Get the current page position.
5064                 val currentPosition = webViewStateAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
5065
5066                 // Get the current tab.
5067                 val tab = tabLayout.getTabAt(currentPosition)
5068
5069                 // Only populate the title text view if the tab has been fully created.
5070                 if (tab != null) {
5071                     // Get the custom view from the tab.
5072                     val tabView = tab.customView
5073
5074                     // Only populate the title text view if the tab view has been fully populated.
5075                     if (tabView != null) {
5076                         // Get the title text view from the tab.
5077                         val tabTitleTextView = tabView.findViewById<TextView>(R.id.title_textview)
5078
5079                         // Set the title according to the URL.
5080                         if (title == "about:blank") {
5081                             // Set the title to indicate a new tab.
5082                             tabTitleTextView.setText(R.string.new_tab)
5083                         } else {
5084                             // Set the title as the tab text.
5085                             tabTitleTextView.text = title
5086                         }
5087                     }
5088                 }
5089             }
5090
5091             // Enter full screen video.
5092             override fun onShowCustomView(video: View, callback: CustomViewCallback) {
5093                 // Set the full screen video flag.
5094                 displayingFullScreenVideo = true
5095
5096                 // Hide the keyboard.
5097                 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.windowToken, 0)
5098
5099                 // Hide the coordinator layout.
5100                 coordinatorLayout.visibility = View.GONE
5101
5102                 /* Hide the system bars.
5103                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5104                  * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5105                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5106                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5107                  */
5108
5109                 // The deprecated command can be switched to `WindowInsetsController` once the minimum API >= 30.
5110                 @Suppress("DEPRECATION")
5111                 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
5112
5113                 // Disable the sliding drawers.
5114                 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
5115
5116                 // Add the video view to the full screen video frame layout.
5117                 fullScreenVideoFrameLayout.addView(video)
5118
5119                 // Show the full screen video frame layout.
5120                 fullScreenVideoFrameLayout.visibility = View.VISIBLE
5121
5122                 // Disable the screen timeout while the video is playing.  YouTube does this automatically, but not all other videos do.
5123                 fullScreenVideoFrameLayout.keepScreenOn = true
5124             }
5125
5126             // Exit full screen video.
5127             override fun onHideCustomView() {
5128                 // Exit the full screen video.
5129                 exitFullScreenVideo()
5130             }
5131
5132             // Upload files.
5133             override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
5134                 // Store the file path callback.
5135                 fileChooserCallback = filePathCallback
5136
5137                 // Create an intent to open a chooser based on the file chooser parameters.
5138                 val fileChooserIntent = fileChooserParams.createIntent()
5139
5140                 // Check to see if the file chooser intent resolves to an installed package.
5141                 if (fileChooserIntent.resolveActivity(packageManager) != null) {  // The file chooser intent is fine.
5142                     // Launch the file chooser intent.
5143                     browseFileUploadActivityResultLauncher.launch(fileChooserIntent)
5144                 } else {  // The file chooser intent will cause a crash.
5145                     // Create a generic intent to open a chooser.
5146                     val genericFileChooserIntent = Intent(Intent.ACTION_GET_CONTENT)
5147
5148                     // Request an openable file.
5149                     genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE)
5150
5151                     // Set the file type to everything.
5152                     genericFileChooserIntent.type = "*/*"
5153
5154                     // Launch the generic file chooser intent.
5155                     browseFileUploadActivityResultLauncher.launch(genericFileChooserIntent)
5156                 }
5157
5158                 // Handle the event.
5159                 return true
5160             }
5161         }
5162         nestedScrollWebView.webViewClient = object : WebViewClient() {
5163             // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
5164             override fun shouldOverrideUrlLoading(view: WebView, webResourceRequest: WebResourceRequest): Boolean {
5165                 // Get the URL from the web resource request.
5166                 var requestUrlString = webResourceRequest.url.toString()
5167
5168                 // Sanitize the url.
5169                 requestUrlString = sanitizeUrl(requestUrlString)
5170
5171                 // Handle the URL according to the type.
5172                 return if (requestUrlString.startsWith("http")) {  // Load the URL in Privacy Browser.
5173                     // Load the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
5174                     loadUrl(nestedScrollWebView, requestUrlString)
5175
5176                     // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
5177                     // Custom headers cannot be added if false is returned and the WebView handles the loading of the URL.
5178                     true
5179                 } else if (requestUrlString.startsWith("mailto:")) {  // Load the email address in an external email program.
5180                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
5181                     val emailIntent = Intent(Intent.ACTION_SENDTO)
5182
5183                     // Parse the url and set it as the data for the intent.
5184                     emailIntent.data = Uri.parse(requestUrlString)
5185
5186                     // Open the email program in a new task instead of as part of Privacy Browser.
5187                     emailIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
5188
5189                     try {
5190                         // Make it so.
5191                         startActivity(emailIntent)
5192                     } catch (exception: ActivityNotFoundException) {
5193                         // Display a snackbar.
5194                         Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
5195                     }
5196
5197                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5198                     true
5199                 } else if (requestUrlString.startsWith("tel:")) {  // Load the phone number in the dialer.
5200                     // Create a dial intent.
5201                     val dialIntent = Intent(Intent.ACTION_DIAL)
5202
5203                     // Add the phone number to the intent.
5204                     dialIntent.data = Uri.parse(requestUrlString)
5205
5206                     // Open the dialer in a new task instead of as part of Privacy Browser.
5207                     dialIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
5208
5209                     try {
5210                         // Make it so.
5211                         startActivity(dialIntent)
5212                     } catch (exception: ActivityNotFoundException) {
5213                         // Display a snackbar.
5214                         Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
5215                     }
5216
5217                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5218                     true
5219                 } else {  // Load a system chooser to select an app that can handle the URL.
5220                     // Create a generic intent to open an app.
5221                     val genericIntent = Intent(Intent.ACTION_VIEW)
5222
5223                     // Add the URL to the intent.
5224                     genericIntent.data = Uri.parse(requestUrlString)
5225
5226                     // List all apps that can handle the URL instead of just opening the first one.
5227                     genericIntent.addCategory(Intent.CATEGORY_BROWSABLE)
5228
5229                     // Open the app in a new task instead of as part of Privacy Browser.
5230                     genericIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
5231
5232                     try {
5233                         // Make it so.
5234                         startActivity(genericIntent)
5235                     } catch (exception: ActivityNotFoundException) {
5236                         // Display a snackbar.
5237                         Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url, requestUrlString), Snackbar.LENGTH_SHORT).show()
5238                     }
5239
5240                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5241                     true
5242                 }
5243             }
5244
5245             // Check requests against the block lists.
5246             override fun shouldInterceptRequest(view: WebView, webResourceRequest: WebResourceRequest): WebResourceResponse? {
5247                 // Get the URL.
5248                 val requestUrlString = webResourceRequest.url.toString()
5249
5250                 // Check to see if the resource request is for the main URL.
5251                 if (requestUrlString == nestedScrollWebView.currentUrl) {
5252                     // `return null` loads the resource request, which should never be blocked if it is the main URL.
5253                     return null
5254                 }
5255
5256                 // 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.
5257                 while (ultraPrivacy == null) {
5258                     try {
5259                         // Check to see if the filter lists have been populated after 100 ms.
5260                         Thread.sleep(100)
5261                     } catch (exception: InterruptedException) {
5262                         // Do nothing.
5263                     }
5264                 }
5265
5266                 // Create an empty web resource response to be used if the resource request is blocked.
5267                 val emptyWebResourceResponse = WebResourceResponse("text/plain", "utf8", ByteArrayInputStream("".toByteArray()))
5268
5269                 // Initialize the variables.
5270                 var allowListResultStringArray: Array<String>? = null
5271                 var isThirdPartyRequest = false
5272
5273                 // Get the current URL.  `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
5274                 var currentBaseDomain = nestedScrollWebView.currentDomainName
5275
5276                 // Store a copy of the current domain for use in later requests.
5277                 val currentDomain = currentBaseDomain
5278
5279                 // Get the request host name.
5280                 var requestBaseDomain = webResourceRequest.url.host
5281
5282                 // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
5283                 if (currentBaseDomain.isNotEmpty() && (requestBaseDomain != null)) {
5284                     // Determine the current base domain.
5285                     while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
5286                         // Remove the first subdomain.
5287                         currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1)
5288                     }
5289
5290                     // Determine the request base domain.
5291                     while (requestBaseDomain!!.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
5292                         // Remove the first subdomain.
5293                         requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1)
5294                     }
5295
5296                     // Update the third party request tracker.
5297                     isThirdPartyRequest = currentBaseDomain != requestBaseDomain
5298                 }
5299
5300                 // Get the current WebView page position.
5301                 val webViewPagePosition = webViewStateAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
5302
5303                 // Determine if the WebView is currently displayed.
5304                 val webViewDisplayed = (webViewPagePosition == tabLayout.selectedTabPosition)
5305
5306                 // Block third-party requests if enabled.
5307                 if (isThirdPartyRequest && nestedScrollWebView.blockAllThirdPartyRequests) {
5308                     // Add the result to the resource requests.
5309                     nestedScrollWebView.addResourceRequest(arrayOf(REQUEST_THIRD_PARTY, requestUrlString))
5310
5311                     // Increment the blocked requests counters.
5312                     nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5313                     nestedScrollWebView.incrementRequestsCount(THIRD_PARTY_REQUESTS)
5314
5315                     // Update the titles of the filter lists menu items if the WebView is currently displayed.
5316                     if (webViewDisplayed) {
5317                         // Updating the UI must be run from the UI thread.
5318                         runOnUiThread {
5319                             // Update the menu item titles.
5320                             navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5321
5322                             // Update the options menu if it has been populated.
5323                             if (optionsMenu != null) {
5324                                 optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5325                                 optionsBlockAllThirdPartyRequestsMenuItem.title =
5326                                     nestedScrollWebView.getRequestsCount(THIRD_PARTY_REQUESTS).toString() + " - " + getString(R.string.block_all_third_party_requests)
5327                             }
5328                         }
5329                     }
5330
5331                     // The resource request was blocked.  Return an empty web resource response.
5332                     return emptyWebResourceResponse
5333                 }
5334
5335                 // Check UltraList if it is enabled.
5336                 if (nestedScrollWebView.ultraListEnabled) {
5337                     // Check the URL against UltraList.
5338                     val ultraListResults = checkFilterListHelper.checkFilterList(currentDomain, requestUrlString, isThirdPartyRequest, ultraList)
5339
5340                     // Process the UltraList results.
5341                     if (ultraListResults[0] == REQUEST_BLOCKED) {  // The resource request matched UltraList's block list.
5342                         // Add the result to the resource requests.
5343                         nestedScrollWebView.addResourceRequest(arrayOf(ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]))
5344
5345                         // Increment the blocked requests counters.
5346                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5347                         nestedScrollWebView.incrementRequestsCount(com.stoutner.privacybrowser.views.ULTRALIST)
5348
5349                         // Update the titles of the filter lists menu items if the WebView is currently displayed.
5350                         if (webViewDisplayed) {
5351                             // Updating the UI must be run from the UI thread.
5352                             runOnUiThread {
5353                                 // Update the menu item titles.
5354                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5355
5356                                 // Update the options menu if it has been populated.
5357                                 if (optionsMenu != null) {
5358                                     optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5359                                     optionsUltraListMenuItem.title = nestedScrollWebView.getRequestsCount(com.stoutner.privacybrowser.views.ULTRALIST).toString() + " - " + getString(R.string.ultralist)
5360                                 }
5361                             }
5362                         }
5363
5364                         // The resource request was blocked.  Return an empty web resource response.
5365                         return emptyWebResourceResponse
5366                     } else if (ultraListResults[0] == REQUEST_ALLOWED) {  // The resource request matched UltraList's allow list.
5367                         // Add an allow list entry to the resource requests array.
5368                         nestedScrollWebView.addResourceRequest(arrayOf(ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]))
5369
5370                         // The resource request has been allowed by UltraList.  `return null` loads the requested resource.
5371                         return null
5372                     }
5373                 }
5374
5375                 // Check UltraPrivacy if it is enabled.
5376                 if (nestedScrollWebView.ultraPrivacyEnabled) {
5377                     // Check the URL against UltraPrivacy.
5378                     val ultraPrivacyResults = checkFilterListHelper.checkFilterList(currentDomain, requestUrlString, isThirdPartyRequest, ultraPrivacy!!)
5379
5380                     // Process the UltraPrivacy results.
5381                     if (ultraPrivacyResults[0] == REQUEST_BLOCKED) {  // The resource request matched UltraPrivacy's block list.
5382                         // Add the result to the resource requests.
5383                         nestedScrollWebView.addResourceRequest(arrayOf(ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5384                             ultraPrivacyResults[5]))
5385
5386                         // Increment the blocked requests counters.
5387                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5388                         nestedScrollWebView.incrementRequestsCount(ULTRAPRIVACY)
5389
5390                         // Update the titles of the filter lists menu items if the WebView is currently displayed.
5391                         if (webViewDisplayed) {
5392                             // Updating the UI must be run from the UI thread.
5393                             runOnUiThread {
5394                                 // Update the menu item titles.
5395                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5396
5397                                 // Update the options menu if it has been populated.
5398                                 if (optionsMenu != null) {
5399                                     optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5400                                     optionsUltraPrivacyMenuItem.title = nestedScrollWebView.getRequestsCount(ULTRAPRIVACY).toString() + " - " + getString(R.string.ultraprivacy)
5401                                 }
5402                             }
5403                         }
5404
5405                         // The resource request was blocked.  Return an empty web resource response.
5406                         return emptyWebResourceResponse
5407                     } else if (ultraPrivacyResults[0] == REQUEST_ALLOWED) {  // The resource request matched UltraPrivacy's allow list.
5408                         // Add an allow list entry to the resource requests array.
5409                         nestedScrollWebView.addResourceRequest(arrayOf(ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5410                             ultraPrivacyResults[5]))
5411
5412                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
5413                         return null
5414                     }
5415                 }
5416
5417                 // Check EasyList if it is enabled.
5418                 if (nestedScrollWebView.easyListEnabled) {
5419                     // Check the URL against EasyList.
5420                     val easyListResults = checkFilterListHelper.checkFilterList(currentDomain, requestUrlString, isThirdPartyRequest, easyList)
5421
5422                     // Process the EasyList results.
5423                     if (easyListResults[0] == REQUEST_BLOCKED) {  // The resource request matched EasyList's block list.
5424                         // Add the result to the resource requests.
5425                         nestedScrollWebView.addResourceRequest(arrayOf(easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]))
5426
5427                         // Increment the blocked requests counters.
5428                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5429                         nestedScrollWebView.incrementRequestsCount(EASYLIST)
5430
5431                         // Update the titles of the filter lists menu items if the WebView is currently displayed.
5432                         if (webViewDisplayed) {
5433                             // Updating the UI must be run from the UI thread.
5434                             runOnUiThread {
5435                                 // Update the menu item titles.
5436                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5437
5438                                 // Update the options menu if it has been populated.
5439                                 if (optionsMenu != null) {
5440                                     optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5441                                     optionsEasyListMenuItem.title = nestedScrollWebView.getRequestsCount(EASYLIST).toString() + " - " + getString(R.string.easylist)
5442                                 }
5443                             }
5444                         }
5445
5446                         // The resource request was blocked.  Return an empty web resource response.
5447                         return emptyWebResourceResponse
5448                     } else if (easyListResults[0] == REQUEST_ALLOWED) {  // The resource request matched EasyList's allow list.
5449                         // Update the allow list result string array tracker.
5450                         allowListResultStringArray = arrayOf(easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5])
5451                     }
5452                 }
5453
5454                 // Check EasyPrivacy if it is enabled.
5455                 if (nestedScrollWebView.easyPrivacyEnabled) {
5456                     // Check the URL against EasyPrivacy.
5457                     val easyPrivacyResults = checkFilterListHelper.checkFilterList(currentDomain, requestUrlString, isThirdPartyRequest, easyPrivacy)
5458
5459                     // Process the EasyPrivacy results.
5460                     if (easyPrivacyResults[0] == REQUEST_BLOCKED) {  // The resource request matched EasyPrivacy's block list.
5461                         // Add the result to the resource requests.
5462                         nestedScrollWebView.addResourceRequest(arrayOf(easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]))
5463
5464                         // Increment the blocked requests counters.
5465                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5466                         nestedScrollWebView.incrementRequestsCount(EASYPRIVACY)
5467
5468                         // Update the titles of the filter lists menu items if the WebView is currently displayed.
5469                         if (webViewDisplayed) {
5470                             // Updating the UI must be run from the UI thread.
5471                             runOnUiThread {
5472                                 // Update the menu item titles.
5473                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5474
5475                                 // Update the options menu if it has been populated.
5476                                 if (optionsMenu != null) {
5477                                     optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5478                                     optionsEasyPrivacyMenuItem.title = nestedScrollWebView.getRequestsCount(EASYPRIVACY).toString() + " - " + getString(R.string.easyprivacy)
5479                                 }
5480                             }
5481                         }
5482
5483                         // The resource request was blocked.  Return an empty web resource response.
5484                         return emptyWebResourceResponse
5485                     } else if (easyPrivacyResults[0] == REQUEST_ALLOWED) {  // The resource request matched EasyPrivacy's allow list.
5486                         // Update the allow list result string array tracker.
5487                         allowListResultStringArray = arrayOf(easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5])
5488                     }
5489                 }
5490
5491                 // Check Fanboy’s Annoyance List if it is enabled.
5492                 if (nestedScrollWebView.fanboysAnnoyanceListEnabled) {
5493                     // Check the URL against Fanboy's Annoyance List.
5494                     val fanboysAnnoyanceListResults = checkFilterListHelper.checkFilterList(currentDomain, requestUrlString, isThirdPartyRequest, fanboysAnnoyanceList)
5495
5496                     // Process the Fanboy's Annoyance List results.
5497                     if (fanboysAnnoyanceListResults[0] == REQUEST_BLOCKED) {  // The resource request matched Fanboy's Annoyance List's block list.
5498                         // Add the result to the resource requests.
5499                         nestedScrollWebView.addResourceRequest(arrayOf(fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5500                             fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]))
5501
5502                         // Increment the blocked requests counters.
5503                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5504                         nestedScrollWebView.incrementRequestsCount(FANBOYS_ANNOYANCE_LIST)
5505
5506                         // Update the titles of the filter lists menu items if the WebView is currently displayed.
5507                         if (webViewDisplayed) {
5508                             // Updating the UI must be run from the UI thread.
5509                             runOnUiThread {
5510                                 // Update the menu item titles.
5511                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5512
5513                                 // Update the options menu if it has been populated.
5514                                 if (optionsMenu != null) {
5515                                     optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5516                                     optionsFanboysAnnoyanceListMenuItem.title = nestedScrollWebView.getRequestsCount(FANBOYS_ANNOYANCE_LIST).toString() + " - " + getString(R.string.fanboys_annoyance_list)
5517                                 }
5518                             }
5519                         }
5520
5521                         // The resource request was blocked.  Return an empty web resource response.
5522                         return emptyWebResourceResponse
5523                     } else if (fanboysAnnoyanceListResults[0] == REQUEST_ALLOWED) {  // The resource request matched Fanboy's Annoyance List's allow list.
5524                         // Update the allow list result string array tracker.
5525                         allowListResultStringArray = arrayOf(fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5526                             fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5])
5527                     }
5528                 } else if (nestedScrollWebView.fanboysSocialBlockingListEnabled) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
5529                     // Check the URL against Fanboy's Annoyance List.
5530                     val fanboysSocialListResults = checkFilterListHelper.checkFilterList(currentDomain, requestUrlString, isThirdPartyRequest, fanboysSocialList)
5531
5532                     // Process the Fanboy's Social Blocking List results.
5533                     if (fanboysSocialListResults[0] == REQUEST_BLOCKED) {  // The resource request matched Fanboy's Social Blocking List's block list.
5534                         // Add the result to the resource requests.
5535                         nestedScrollWebView.addResourceRequest(arrayOf(fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5536                             fanboysSocialListResults[4], fanboysSocialListResults[5]))
5537
5538                         // Increment the blocked requests counters.
5539                         nestedScrollWebView.incrementRequestsCount(BLOCKED_REQUESTS)
5540                         nestedScrollWebView.incrementRequestsCount(FANBOYS_SOCIAL_BLOCKING_LIST)
5541
5542                         // Update the titles of the filter lists menu items if the WebView is currently displayed.
5543                         if (webViewDisplayed) {
5544                             // Updating the UI must be run from the UI thread.
5545                             runOnUiThread {
5546                                 // Update the menu item titles.
5547                                 navigationRequestsMenuItem.title = getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5548
5549                                 // Update the options menu if it has been populated.
5550                                 if (optionsMenu != null) {
5551                                     optionsFilterListsMenuItem.title = getString(R.string.filterlists) + " - " + nestedScrollWebView.getRequestsCount(BLOCKED_REQUESTS)
5552                                     optionsFanboysSocialBlockingListMenuItem.title =
5553                                         nestedScrollWebView.getRequestsCount(FANBOYS_SOCIAL_BLOCKING_LIST).toString() + " - " + getString(R.string.fanboys_social_blocking_list)
5554                                 }
5555                             }
5556                         }
5557
5558                         // The resource request was blocked.  Return an empty web resource response.
5559                         return emptyWebResourceResponse
5560                     } else if (fanboysSocialListResults[0] == REQUEST_ALLOWED) {  // The resource request matched Fanboy's Social Blocking List's allow list.
5561                         // Update the allow list result string array tracker.
5562                         allowListResultStringArray = arrayOf(fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3], fanboysSocialListResults[4],
5563                             fanboysSocialListResults[5])
5564                     }
5565                 }
5566
5567                 // Add the request to the log because it hasn't been processed by any of the previous checks.
5568                 if (allowListResultStringArray != null) {  // The request was processed by an allow list.
5569                     nestedScrollWebView.addResourceRequest(allowListResultStringArray)
5570                 } else {  // The request didn't match any filter list entry.  Log it as a default request.
5571                     nestedScrollWebView.addResourceRequest(arrayOf(REQUEST_DEFAULT, requestUrlString))
5572                 }
5573
5574                 // The resource request has not been blocked.  `return null` loads the requested resource.
5575                 return null
5576             }
5577
5578             // Handle HTTP authentication requests.
5579             override fun onReceivedHttpAuthRequest(view: WebView, handler: HttpAuthHandler, host: String, realm: String) {
5580                 // Store the handler.
5581                 nestedScrollWebView.httpAuthHandler = handler
5582
5583                 // Instantiate an HTTP authentication dialog.
5584                 val httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.webViewFragmentId)
5585
5586                 // Show the HTTP authentication dialog.
5587                 httpAuthenticationDialogFragment.show(supportFragmentManager, getString(R.string.http_authentication))
5588             }
5589
5590             override fun onPageStarted(webView: WebView, url: String, favicon: Bitmap?) {
5591                 // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
5592                 // 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.
5593                 if (appBarLayout.height > 0)
5594                     appBarHeight = appBarLayout.height
5595
5596                 // Set the padding and layout settings according to the position of the app bar.
5597                 if (bottomAppBar) {  // The app bar is on the bottom.
5598                     // Adjust the UI.
5599                     if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
5600                         // Reset the WebView padding to fill the available space.
5601                         swipeRefreshLayout.setPadding(0, 0, 0, 0)
5602                     } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
5603                         // Move the WebView above the app bar layout.
5604                         swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight)
5605                     }
5606                 } else {  // The app bar is on the top.
5607                     // 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.
5608                     if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
5609                         // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
5610                         swipeRefreshLayout.setPadding(0, 0, 0, 0)
5611
5612                         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5613                         swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset)
5614                     } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
5615                         // The swipe refresh layout must be manually moved below the app bar layout.
5616                         swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0)
5617
5618                         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5619                         swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight)
5620                     }
5621                 }
5622
5623                 // Reset the list of resource requests.
5624                 nestedScrollWebView.clearResourceRequests()
5625
5626                 // Reset the requests counters.
5627                 nestedScrollWebView.resetRequestsCounters()
5628
5629                 // Get the current page position.
5630                 val currentPagePosition = webViewStateAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
5631
5632                 // Update the URL text bar if the page is currently selected and the URL edit text is not currently being edited.
5633                 if ((tabLayout.selectedTabPosition == currentPagePosition) && !urlEditText.hasFocus()) {
5634                     // Display the formatted URL text.  The nested scroll WebView current URL preserves any initial `view-source:`, and opposed to the method URL variable.
5635                     urlEditText.setText(nestedScrollWebView.currentUrl)
5636
5637                     // Highlight the URL syntax.
5638                     UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
5639
5640                     // Hide the keyboard.
5641                     inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.windowToken, 0)
5642                 }
5643
5644                 // Reset the list of host IP addresses.
5645                 nestedScrollWebView.currentIpAddresses = ""
5646
5647                 // Get a URI for the current URL.
5648                 val currentUri = Uri.parse(url)
5649
5650                 // Get the current domain name.
5651                 val currentDomainName = currentUri.host
5652
5653                 // Get the IP addresses for the current domain.
5654                 if (!currentDomainName.isNullOrEmpty())
5655                     GetHostIpAddressesCoroutine.checkPinnedMismatch(currentDomainName, nestedScrollWebView, supportFragmentManager, getString(R.string.pinned_mismatch))
5656
5657                 // 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.)
5658                 if ((optionsMenu != null) && (webView == currentWebView)) {
5659                     // Set the title.
5660                     optionsRefreshMenuItem.setTitle(R.string.stop)
5661
5662                     // Set the icon if it is displayed in the AppBar.
5663                     if (displayAdditionalAppBarIcons)
5664                         optionsRefreshMenuItem.setIcon(R.drawable.close_blue)
5665                 }
5666             }
5667
5668             override fun onPageFinished(webView: WebView, url: String) {
5669                 // Flush any cookies to persistent storage.  The cookie manager has become very lazy about flushing cookies in recent versions.
5670                 if (nestedScrollWebView.acceptCookies)
5671                     cookieManager.flush()
5672
5673                 // Update the Refresh menu item if the options menu has been created and the WebView is currently displayed.
5674                 if (optionsMenu != null && (webView == currentWebView)) {
5675                     // Reset the Refresh title.
5676                     optionsRefreshMenuItem.setTitle(R.string.refresh)
5677
5678                     // Reset the icon if it is displayed in the app bar.
5679                     if (displayAdditionalAppBarIcons)
5680                         optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled)
5681                 }
5682
5683                 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
5684                 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
5685                 val privateDataDirectoryString = applicationInfo.dataDir
5686
5687                 // Clear the cache, history, and logcat if Incognito Mode is enabled.
5688                 if (incognitoModeEnabled) {
5689                     // Clear the cache.  `true` includes disk files.
5690                     nestedScrollWebView.clearCache(true)
5691
5692                     // Clear the back/forward history.
5693                     nestedScrollWebView.clearHistory()
5694
5695                     // Manually delete cache folders.
5696                     try {
5697                         // Delete the main cache directory.
5698                         Runtime.getRuntime().exec("rm -rf $privateDataDirectoryString/cache")
5699                     } catch (exception: IOException) {
5700                         // Do nothing if an error is thrown.
5701                     }
5702
5703                     // Clear the logcat.
5704                     try {
5705                         // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
5706                         Runtime.getRuntime().exec("logcat -b all -c")
5707                     } catch (exception: IOException) {
5708                         // Do nothing.
5709                     }
5710                 }
5711
5712                 // Clear the `Service Worker` directory.
5713                 try {
5714                     // A string array must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
5715                     Runtime.getRuntime().exec(arrayOf("rm", "-rf", "$privateDataDirectoryString/app_webview/Default/Service Worker/"))
5716                 } catch (exception: IOException) {
5717                     // Do nothing.
5718                 }
5719
5720                 // Get the current page position.
5721                 val currentPagePosition = webViewStateAdapter!!.getPositionForId(nestedScrollWebView.webViewFragmentId)
5722
5723                 // 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.
5724                 val currentUrl = nestedScrollWebView.url
5725
5726                 // Get the current tab.
5727                 val tab = tabLayout.getTabAt(currentPagePosition)
5728
5729                 // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
5730                 // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
5731                 // Probably some sort of race condition when Privacy Browser is being resumed.
5732                 if ((tabLayout.selectedTabPosition == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) {
5733                     // Check to see if the URL is `about:blank`.
5734                     if (currentUrl == "about:blank") {  // The WebView is blank.
5735                         // Display the hint in the URL edit text.
5736                         urlEditText.setText("")
5737
5738                         // Request focus for the URL text box.
5739                         urlEditText.requestFocus()
5740
5741                         // Display the keyboard.
5742                         inputMethodManager.showSoftInput(urlEditText, 0)
5743
5744                         // Apply the domain settings.  This clears any settings from the previous domain.
5745                         applyDomainSettings(nestedScrollWebView, "", resetTab = true, reloadWebsite = false, loadUrl = false)
5746
5747                         // Only populate the title text view if the tab has been fully created.
5748                         if (tab != null) {
5749                             // Get the custom view from the tab.
5750                             val tabView = tab.customView!!
5751
5752                             // Get the title text view from the tab.
5753                             val tabTitleTextView = tabView.findViewById<TextView>(R.id.title_textview)
5754
5755                             // Set the title as the tab text.
5756                             tabTitleTextView.setText(R.string.new_tab)
5757                         }
5758                     } else {  // The WebView has loaded a webpage.
5759                         // Update the URL edit text if it is not currently being edited.
5760                         if (!urlEditText.hasFocus()) {
5761                             // 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.
5762                             val sanitizedUrl = sanitizeUrl(currentUrl)
5763
5764                             // Display the final URL.  Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
5765                             urlEditText.setText(sanitizedUrl)
5766
5767                             // Highlight the URL syntax.
5768                             UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
5769                         }
5770
5771                         // Only populate the title text view if the tab has been fully created.
5772                         if (tab != null) {
5773                             // Get the custom view from the tab.
5774                             val tabView = tab.customView!!
5775
5776                             // Get the title text view from the tab.
5777                             val tabTitleTextView = tabView.findViewById<TextView>(R.id.title_textview)
5778
5779                             // Set the title as the tab text.  Sometimes `onReceivedTitle()` is not called, especially when navigating history.
5780                             tabTitleTextView.text = nestedScrollWebView.title
5781                         }
5782                     }
5783                 }
5784             }
5785
5786             // Handle SSL Certificate errors.  Suppress the lint warning that ignoring the error might be dangerous.
5787             @SuppressLint("WebViewClientOnReceivedSslError")
5788             override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
5789                 // Get the current website SSL certificate.
5790                 val currentWebsiteSslCertificate = error.certificate
5791
5792                 // Extract the individual pieces of information from the current website SSL certificate.
5793                 val currentWebsiteIssuedToCName = currentWebsiteSslCertificate.issuedTo.cName
5794                 val currentWebsiteIssuedToOName = currentWebsiteSslCertificate.issuedTo.oName
5795                 val currentWebsiteIssuedToUName = currentWebsiteSslCertificate.issuedTo.uName
5796                 val currentWebsiteIssuedByCName = currentWebsiteSslCertificate.issuedBy.cName
5797                 val currentWebsiteIssuedByOName = currentWebsiteSslCertificate.issuedBy.oName
5798                 val currentWebsiteIssuedByUName = currentWebsiteSslCertificate.issuedBy.uName
5799                 val currentWebsiteSslStartDate = currentWebsiteSslCertificate.validNotBeforeDate
5800                 val currentWebsiteSslEndDate = currentWebsiteSslCertificate.validNotAfterDate
5801
5802                 // Get the pinned SSL certificate.
5803                 val (pinnedSslCertificateStringArray, pinnedSslCertificateDateArray) = nestedScrollWebView.getPinnedSslCertificate()
5804
5805                 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
5806                 if (nestedScrollWebView.hasPinnedSslCertificate() &&
5807                     (currentWebsiteIssuedToCName == pinnedSslCertificateStringArray[0]) &&
5808                     (currentWebsiteIssuedToOName == pinnedSslCertificateStringArray[1]) &&
5809                     (currentWebsiteIssuedToUName == pinnedSslCertificateStringArray[2]) &&
5810                     (currentWebsiteIssuedByCName == pinnedSslCertificateStringArray[3]) &&
5811                     (currentWebsiteIssuedByOName == pinnedSslCertificateStringArray[4]) &&
5812                     (currentWebsiteIssuedByUName == pinnedSslCertificateStringArray[5]) &&
5813                     (currentWebsiteSslStartDate == pinnedSslCertificateDateArray[0]) &&
5814                     (currentWebsiteSslEndDate == pinnedSslCertificateDateArray[1])) {
5815
5816                     // An SSL certificate is pinned and matches the current domain certificate.  Proceed to the website without displaying an error.
5817                     handler.proceed()
5818                 } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
5819                     // Store the SSL error handler.
5820                     nestedScrollWebView.sslErrorHandler = handler
5821
5822                     // Instantiate an SSL certificate error alert dialog.
5823                     val sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.webViewFragmentId)
5824
5825                     // 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.
5826                     try {
5827                         // Show the SSL certificate error dialog.
5828                         sslCertificateErrorDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate_error))
5829                     } catch (exception: Exception) {
5830                         // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
5831                         pendingDialogsArrayList.add(PendingDialogDataClass(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)))
5832                     }
5833                 }
5834             }
5835         }
5836
5837         // Check to see if the state is being restored.
5838         if (restoringState) {  // The state is being restored.
5839             // Resume the nested scroll WebView JavaScript timers.
5840             nestedScrollWebView.resumeTimers()
5841         } else if (pageNumber == 0) {  // The first page is being loaded.
5842             // Set this nested scroll WebView as the current WebView.
5843             currentWebView = nestedScrollWebView
5844
5845             // Get the intent that started the app.
5846             val launchingIntent = intent
5847
5848             // Reset the intent.  This prevents a duplicate tab from being created on restart.
5849             intent = Intent()
5850
5851             // Get the information from the intent.
5852             val launchingIntentAction = launchingIntent.action
5853             val launchingIntentUriData = launchingIntent.data
5854             val launchingIntentStringExtra = launchingIntent.getStringExtra(Intent.EXTRA_TEXT)
5855
5856             // Parse the launching intent URL.  Suppress the suggestions of using elvis expressions as they make the logic very difficult to follow.
5857             @Suppress("IfThenToElvis") val urlToLoadString = if ((launchingIntentAction != null) && (launchingIntentAction == Intent.ACTION_WEB_SEARCH)) {  // The intent contains a search string.
5858                 // Sanitize the search input and convert it to a search.
5859                 val encodedSearchString = try {
5860                     URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8")
5861                 } catch (exception: UnsupportedEncodingException) {
5862                     ""
5863                 }
5864
5865                 // Add the search URL to the encodedSearchString
5866                 searchURL + encodedSearchString
5867             } else if (launchingIntentUriData != null) {  // The launching intent contains a URL formatted as a URI.
5868                 // Get the URL from the URI.
5869                 launchingIntentUriData.toString()
5870             } else if (launchingIntentStringExtra != null) {  // The launching intent contains text that might be a URL.
5871                 // Get the URL from the string extra.
5872                 launchingIntentStringExtra
5873             } else if (urlString != "") {  // The activity has been restarted.
5874                 // Load the saved URL.
5875                 urlString
5876             } else {  // The is no saved URL and there is no URL in the intent.
5877                 // Load the homepage.
5878                 sharedPreferences.getString("homepage", getString(R.string.homepage_default_value))
5879             }
5880
5881             // Load the website if not waiting for the proxy.
5882             if (waitingForProxy) {  // Store the URL to be loaded in the Nested Scroll WebView.
5883                 nestedScrollWebView.waitingForProxyUrlString = urlToLoadString!!
5884             } else {  // Load the URL.
5885                 loadUrl(nestedScrollWebView, urlToLoadString!!)
5886             }
5887         } else {  // This is not the first tab.
5888             // Load the URL.
5889             loadUrl(nestedScrollWebView, urlString)
5890
5891             // Set the focus and display the keyboard if the URL is blank.
5892             if (urlString == "") {
5893                 // Request focus for the URL text box.
5894                 urlEditText.requestFocus()
5895
5896                 // Create a display keyboard handler.
5897                 val displayKeyboardHandler = Handler(Looper.getMainLooper())
5898
5899                 // Create a display keyboard runnable.
5900                 val displayKeyboardRunnable = Runnable {
5901                     // Display the keyboard.
5902                     inputMethodManager.showSoftInput(urlEditText, 0)
5903                 }
5904
5905                 // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition.
5906                 displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100)
5907             }
5908         }
5909     }
5910
5911     private fun loadBookmarksFolder() {
5912         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
5913         bookmarksCursor = bookmarksDatabaseHelper!!.getBookmarksByDisplayOrder(currentBookmarksFolderId)
5914
5915         // Populate the bookmarks cursor adapter.
5916         bookmarksCursorAdapter = object : CursorAdapter(this, bookmarksCursor, false) {
5917             override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
5918                 // Inflate the individual item layout.
5919                 return layoutInflater.inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false)
5920             }
5921
5922             override fun bindView(view: View, context: Context, cursor: Cursor) {
5923                 // Get handles for the views.
5924                 val bookmarkFavoriteIcon = view.findViewById<ImageView>(R.id.bookmark_favorite_icon)
5925                 val bookmarkNameTextView = view.findViewById<TextView>(R.id.bookmark_name)
5926
5927                 // Get the favorite icon byte array from the cursor.
5928                 val favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(FAVORITE_ICON))
5929
5930                 // Convert the byte array to a bitmap beginning at the first byte and ending at the last.
5931                 val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
5932
5933                 // Display the bitmap in the bookmark favorite icon.
5934                 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap)
5935
5936                 // Display the bookmark name from the cursor in the bookmark name text view.
5937                 bookmarkNameTextView.text = cursor.getString(cursor.getColumnIndexOrThrow(BOOKMARK_NAME))
5938
5939                 // Make the font bold for folders.
5940                 if (cursor.getInt(cursor.getColumnIndexOrThrow(IS_FOLDER)) == 1)
5941                     bookmarkNameTextView.typeface = Typeface.DEFAULT_BOLD
5942                 else  // Reset the font to default for normal bookmarks.
5943                     bookmarkNameTextView.typeface = Typeface.DEFAULT
5944             }
5945         }
5946
5947         // Populate the list view with the adapter.
5948         bookmarksListView.adapter = bookmarksCursorAdapter
5949
5950         // Set the bookmarks drawer title.
5951         if (currentBookmarksFolderId == HOME_FOLDER_ID)  // The current bookmarks folder is the home folder.
5952             bookmarksTitleTextView.setText(R.string.bookmarks)
5953         else
5954             bookmarksTitleTextView.text = bookmarksDatabaseHelper!!.getFolderName(currentBookmarksFolderId)
5955     }
5956
5957     private fun loadUrl(nestedScrollWebView: NestedScrollWebView, url: String) {
5958         // Sanitize the URL.
5959         val urlString = sanitizeUrl(url)
5960
5961         // Apply the domain settings and load the URL.
5962         applyDomainSettings(nestedScrollWebView, urlString, resetTab = true, reloadWebsite = false, loadUrl = true)
5963     }
5964
5965     private fun loadUrlFromTextBox() {
5966         // 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.
5967         var unformattedUrlString = urlEditText.text.toString().trim { it <= ' ' }
5968
5969         // Create the formatted URL string.
5970         var urlString = ""
5971
5972         // Check to see if the unformatted URL string is a valid URL.  Otherwise, convert it into a search.
5973         if (unformattedUrlString.startsWith("content://") || unformattedUrlString.startsWith("view-source:")) {  // This is a content or source URL.
5974             // Load the entire content URL.
5975             urlString = unformattedUrlString
5976         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
5977             unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
5978
5979             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
5980             if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://"))
5981                 unformattedUrlString = "https://$unformattedUrlString"
5982
5983             // Initialize the unformatted URL.
5984             var unformattedUrl: URL? = null
5985
5986             // Convert the unformatted URL string to a URL.
5987             try {
5988                 unformattedUrl = URL(unformattedUrlString)
5989             } catch (exception: MalformedURLException) {
5990                 exception.printStackTrace()
5991             }
5992
5993             // Get the components of the URL.
5994             val scheme = unformattedUrl?.protocol
5995             val authority = unformattedUrl?.authority
5996             val path = unformattedUrl?.path
5997             val query = unformattedUrl?.query
5998             val fragment = unformattedUrl?.ref
5999
6000             // Create a URI.
6001             val uri = Uri.Builder()
6002
6003             // Build the URI from the components of the URL.
6004             uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment)
6005
6006             // Decode the URI as a UTF-8 string in.
6007             try {
6008                 urlString = URLDecoder.decode(uri.build().toString(), "UTF-8")
6009             } catch (exception: UnsupportedEncodingException) {
6010                 // Do nothing.  The formatted URL string will remain blank.
6011             }
6012         } else if (unformattedUrlString.isNotEmpty()) {  // This is not a URL, but rather a search string.
6013             // Sanitize the search input.
6014             val encodedSearchString = try {
6015                 URLEncoder.encode(unformattedUrlString, "UTF-8")
6016             } catch (exception: UnsupportedEncodingException) {
6017                 ""
6018             }
6019
6020             // Add the base search URL.
6021             urlString = searchURL + encodedSearchString
6022         }
6023
6024         // 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.
6025         urlEditText.clearFocus()
6026
6027         // Make it so.
6028         loadUrl(currentWebView!!, urlString)
6029     }
6030
6031     override fun navigateHistory(url: String, steps: Int) {
6032         // Apply the domain settings.
6033         applyDomainSettings(currentWebView!!, url, resetTab = false, reloadWebsite = false, loadUrl = false)
6034
6035         // Load the history entry.
6036         currentWebView!!.goBackOrForward(steps)
6037
6038         // Update the URL edit text after a delay.
6039         updateUrlEditTextAfterDelay()
6040     }
6041
6042     override fun openFile(dialogFragment: DialogFragment) {
6043         // Get the dialog.
6044         val dialog = dialogFragment.dialog!!
6045
6046         // Get handles for the views.
6047         val fileNameEditText = dialog.findViewById<EditText>(R.id.file_name_edittext)
6048         val mhtCheckBox = dialog.findViewById<CheckBox>(R.id.mht_checkbox)
6049
6050         // Get the file path string.
6051         val openFilePath = fileNameEditText.text.toString()
6052
6053         // Apply the domain settings.  This resets the favorite icon and removes any domain settings.
6054         applyDomainSettings(currentWebView!!, openFilePath, resetTab = true, reloadWebsite = false, loadUrl = false)
6055
6056         // Open the file according to the type.
6057         if (mhtCheckBox.isChecked) {  // Force opening of an MHT file.
6058             try {
6059                 // Get the MHT file input stream.
6060                 val mhtFileInputStream = contentResolver.openInputStream(Uri.parse(openFilePath))
6061
6062                 // Create a temporary MHT file.
6063                 val temporaryMhtFile = File.createTempFile(TEMPORARY_MHT_FILE, ".mht", cacheDir)
6064
6065                 // Get a file output stream for the temporary MHT file.
6066                 val temporaryMhtFileOutputStream = FileOutputStream(temporaryMhtFile)
6067
6068                 // Create a transfer byte array.
6069                 val transferByteArray = ByteArray(1024)
6070
6071                 // Create an integer to track the number of bytes read.
6072                 var bytesRead: Int
6073
6074                 // Copy the temporary MHT file input stream to the MHT output stream.
6075                 while (mhtFileInputStream!!.read(transferByteArray).also { bytesRead = it } > 0)
6076                     temporaryMhtFileOutputStream.write(transferByteArray, 0, bytesRead)
6077
6078                 // Flush the temporary MHT file output stream.
6079                 temporaryMhtFileOutputStream.flush()
6080
6081                 // Close the streams.
6082                 temporaryMhtFileOutputStream.close()
6083                 mhtFileInputStream.close()
6084
6085                 // Load the temporary MHT file.
6086                 currentWebView!!.loadUrl(temporaryMhtFile.toString())
6087             } catch (exception: Exception) {
6088                 // Display a snackbar.
6089                 Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
6090             }
6091         } else {  // Let the WebView handle opening of the file.
6092             // Open the file.
6093             currentWebView!!.loadUrl(openFilePath)
6094         }
6095     }
6096
6097     private fun openWithApp(url: String) {
6098         // Create an open with app intent with `ACTION_VIEW`.
6099         val openWithAppIntent = Intent(Intent.ACTION_VIEW)
6100
6101         // Set the URI but not the MIME type.  This should open all available apps.
6102         openWithAppIntent.data = Uri.parse(url)
6103
6104         // Flag the intent to open in a new task.
6105         openWithAppIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
6106
6107         // Try the intent.
6108         try {
6109             // Show the chooser.
6110             startActivity(openWithAppIntent)
6111         } catch (exception: ActivityNotFoundException) {  // There are no apps available to open the URL.
6112             // Show a snackbar with the error.
6113             Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
6114         }
6115     }
6116
6117     private fun openWithBrowser(url: String) {
6118
6119         // Create an open with browser intent with `ACTION_VIEW`.
6120         val openWithBrowserIntent = Intent(Intent.ACTION_VIEW)
6121
6122         // Set the URI and the MIME type.  `"text/html"` should load browser options.
6123         openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html")
6124
6125         // Flag the intent to open in a new task.
6126         openWithBrowserIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
6127
6128         // Try the intent.
6129         try {
6130             // Show the chooser.
6131             startActivity(openWithBrowserIntent)
6132         } catch (exception: ActivityNotFoundException) {  // There are no browsers available to open the URL.
6133             // Show a snackbar with the error.
6134             Snackbar.make(currentWebView!!, getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
6135         }
6136     }
6137
6138     override fun pinnedErrorGoBack() {
6139         // Get the current web back forward list.
6140         val webBackForwardList = currentWebView!!.copyBackForwardList()
6141
6142         // Get the previous entry URL.
6143         val previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.currentIndex - 1).url
6144
6145         // Apply the domain settings.
6146         applyDomainSettings(currentWebView!!, previousUrl, resetTab = false, reloadWebsite = false, loadUrl = false)
6147
6148         // Go back.
6149         currentWebView!!.goBack()
6150
6151         // Update the URL edit text after a delay.
6152         updateUrlEditTextAfterDelay()
6153     }
6154
6155     private fun sanitizeUrl(urlString: String): String {
6156         // Initialize a sanitized URL string.
6157         var sanitizedUrlString = urlString
6158
6159         // Sanitize tracking queries.
6160         if (sanitizeTrackingQueries)
6161             sanitizedUrlString = SanitizeUrlHelper.sanitizeTrackingQueries(sanitizedUrlString)
6162
6163         // Sanitize AMP redirects.
6164         if (sanitizeAmpRedirects)
6165             sanitizedUrlString = SanitizeUrlHelper.sanitizeAmpRedirects(sanitizedUrlString)
6166
6167         // Return the sanitized URL string.
6168         return sanitizedUrlString
6169     }
6170
6171     override fun saveUrl(originalUrlString: String, fileNameString: String, dialogFragment: DialogFragment) {
6172         // Store the URL.  This will be used in the save URL activity result launcher.
6173         saveUrlString = if (originalUrlString.startsWith("data:")) {
6174             // Save the original URL.
6175             originalUrlString
6176         } else {
6177             // Get the dialog.
6178             val dialog = dialogFragment.dialog!!
6179
6180             // Get a handle for the dialog URL edit text.
6181             val dialogUrlEditText = dialog.findViewById<EditText>(R.id.url_edittext)
6182
6183             // Get the URL from the edit text, which may have been modified.
6184             dialogUrlEditText.text.toString()
6185         }
6186
6187         // Open the file picker.
6188         saveUrlActivityResultLauncher.launch(fileNameString)
6189     }
6190
6191     private fun setCurrentWebView(pageNumber: Int) {
6192         // Stop the swipe to refresh indicator if it is running
6193         swipeRefreshLayout.isRefreshing = false
6194
6195         // Try to set the current WebView.  This will fail if the WebView has not yet been populated.
6196         try {
6197             // Get the WebView tab fragment.
6198             val webViewTabFragment = webViewStateAdapter!!.getPageFragment(pageNumber)
6199
6200             // Get the fragment view.
6201             val webViewFragmentView = webViewTabFragment.view
6202
6203             // Store the current WebView.
6204             currentWebView = webViewFragmentView!!.findViewById(R.id.nestedscroll_webview)
6205
6206             // Update the status of swipe to refresh.
6207             if (currentWebView!!.swipeToRefresh) {  // Swipe to refresh is enabled.
6208                 // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.  It is updated every time the scroll changes.
6209                 swipeRefreshLayout.isEnabled = (currentWebView!!.scrollY == 0)
6210             } else {  // Swipe to refresh is disabled.
6211                 // Disable the swipe refresh layout.
6212                 swipeRefreshLayout.isEnabled = false
6213             }
6214
6215             // Set the cookie status.
6216             cookieManager.setAcceptCookie(currentWebView!!.acceptCookies)
6217
6218             // Update the privacy icons.  `true` redraws the icons in the app bar.
6219             updatePrivacyIcons(true)
6220
6221             // Get a handle for the input method manager.
6222             val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
6223
6224             // Get the current URL.
6225             val urlString = currentWebView!!.url
6226
6227             // Update the URL edit text if not loading a new intent.  Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
6228             if (!loadingNewIntent) {  // A new intent is not being loaded.
6229                 if ((urlString == null) || (urlString == "about:blank")) {  // The WebView is blank.
6230                     // Display the hint in the URL edit text.
6231                     urlEditText.setText("")
6232
6233                     // Request focus for the URL text box.
6234                     urlEditText.requestFocus()
6235
6236                     // Display the keyboard.
6237                     inputMethodManager.showSoftInput(urlEditText, 0)
6238                 } else {  // The WebView has a loaded URL.
6239                     // Clear the focus from the URL text box.
6240                     urlEditText.clearFocus()
6241
6242                     // Hide the soft keyboard.
6243                     inputMethodManager.hideSoftInputFromWindow(currentWebView!!.windowToken, 0)
6244
6245                     // Display the current URL in the URL text box.
6246                     urlEditText.setText(urlString)
6247
6248                     // Highlight the URL syntax.
6249                     UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan)
6250                 }
6251             } else {  // A new intent is being loaded.
6252                 // Reset the loading new intent flag.
6253                 loadingNewIntent = false
6254             }
6255
6256             // Set the background to indicate the domain settings status.
6257             if (currentWebView!!.domainSettingsApplied) {
6258                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
6259                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.drawable.domain_settings_url_background)
6260             } else {
6261                 // Remove any background on the URL relative layout.
6262                 urlRelativeLayout.background = AppCompatResources.getDrawable(this, R.color.transparent)
6263             }
6264         }  catch (exception: Exception) {
6265             //  Try again in 100 milliseconds if the WebView has not yet been populated.
6266             // Create a handler to set the current WebView.
6267             val setCurrentWebViewHandler = Handler(Looper.getMainLooper())
6268
6269             // Create a runnable to set the current WebView.
6270             val setCurrentWebWebRunnable = Runnable {
6271                 // Set the current WebView.
6272                 setCurrentWebView(pageNumber)
6273             }
6274
6275             // Try setting the current WebView again after 50 milliseconds.
6276             setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 50)
6277         }
6278     }
6279
6280     // The view parameter cannot be removed because it is called from the layout onClick.
6281     fun toggleBookmarksDrawerPinned(@Suppress("UNUSED_PARAMETER")view: View?) {
6282         // Toggle the bookmarks drawer pinned tracker.
6283         bookmarksDrawerPinned = !bookmarksDrawerPinned
6284
6285         // Update the bookmarks drawer pinned image view.
6286         updateBookmarksDrawerPinnedImageView()
6287     }
6288
6289     private fun updateBookmarksDrawerPinnedImageView() {
6290         // Set the current icon.
6291         if (bookmarksDrawerPinned)
6292             bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin_selected)
6293         else
6294             bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin)
6295     }
6296
6297     private fun updateDomainsSettingsSet() {
6298         // Reset the domains settings set.
6299         domainsSettingsSet = HashSet()
6300
6301         // Get a domains cursor.
6302         val domainsCursor = domainsDatabaseHelper!!.domainNameCursorOrderedByDomain
6303
6304         // Get the current count of domains.
6305         val domainsCount = domainsCursor.count
6306
6307         // Get the domain name column index.
6308         val domainNameColumnIndex = domainsCursor.getColumnIndexOrThrow(DOMAIN_NAME)
6309
6310         // Populate the domain settings set.
6311         for (i in 0 until domainsCount) {
6312             // Move the domains cursor to the current row.
6313             domainsCursor.moveToPosition(i)
6314
6315             // Store the domain name in the domain settings set.
6316             domainsSettingsSet.add(domainsCursor.getString(domainNameColumnIndex))
6317         }
6318
6319         // Close the domains cursor.
6320         domainsCursor.close()
6321     }
6322
6323     override fun updateFontSize(dialogFragment: DialogFragment) {
6324         // Get the dialog.
6325         val dialog = dialogFragment.dialog!!
6326
6327         // Get a handle for the font size edit text.
6328         val fontSizeEditText = dialog.findViewById<EditText>(R.id.font_size_edittext)
6329
6330         // Initialize the new font size variable with the current font size.
6331         var newFontSize = currentWebView!!.settings.textZoom
6332
6333         // Get the font size from the edit text.
6334         try {
6335             newFontSize = fontSizeEditText.text.toString().toInt()
6336         } catch (exception: Exception) {
6337             // If the edit text does not contain a valid font size do nothing.
6338         }
6339
6340         // Apply the new font size.
6341         currentWebView!!.settings.textZoom = newFontSize
6342     }
6343
6344     private fun updatePrivacyIcons(runInvalidateOptionsMenu: Boolean) {
6345         // Only update the privacy icons if the options menu and the current WebView have already been populated.
6346         if ((optionsMenu != null) && (currentWebView != null)) {
6347             // Update the privacy icon.
6348             if (currentWebView!!.settings.javaScriptEnabled)  // JavaScript is enabled.
6349                 optionsPrivacyMenuItem.setIcon(R.drawable.javascript_enabled)
6350             else if (currentWebView!!.acceptCookies)  // JavaScript is disabled but cookies are enabled.
6351                 optionsPrivacyMenuItem.setIcon(R.drawable.warning)
6352             else  // All the dangerous features are disabled.
6353                 optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode)
6354
6355             // Update the cookies icon.
6356             if (currentWebView!!.acceptCookies)
6357                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled)
6358             else
6359                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled)
6360
6361             // Update the refresh icon.
6362             if (optionsRefreshMenuItem.title == getString(R.string.refresh))  // The refresh icon is displayed.
6363                 optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled)
6364             else  // The stop icon is displayed.
6365                 optionsRefreshMenuItem.setIcon(R.drawable.close_blue)
6366
6367             // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar.
6368             if (runInvalidateOptionsMenu)
6369                 invalidateOptionsMenu()
6370         }
6371     }
6372
6373     fun updateUrlEditTextAfterDelay() {
6374         // Create a handler to update the URL edit box.
6375         val urlEditTextUpdateHandler = Handler(Looper.getMainLooper())
6376
6377         // Create a runnable to update the URL edit box.
6378         val urlEditTextUpdateRunnable = Runnable {
6379             // Update the URL edit text.
6380             urlEditText.setText(currentWebView!!.url)
6381
6382             // Disable the wide viewport if the source is being viewed.
6383             if (currentWebView!!.url!!.startsWith("view-source:"))
6384                 currentWebView!!.settings.useWideViewPort = false
6385         }
6386
6387         // Update the URL edit text after 50 milliseconds, so that the WebView has enough time to navigate to the new URL.
6388         urlEditTextUpdateHandler.postDelayed(urlEditTextUpdateRunnable, 50)
6389     }
6390 }