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