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