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