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