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