]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Show the bottom app bar when opening a new tab. https://redmine.stoutner.com/issues/729
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index 111cb63416c94f40cd4967122996b55bf491bde1..d2881d7ebe8887c6d299ca1ec9b1b55cdc6b7e44 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2015-2020 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
@@ -21,7 +21,6 @@
 
 package com.stoutner.privacybrowser.activities;
 
-import android.Manifest;
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Dialog;
@@ -31,7 +30,6 @@ import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
 import android.content.ClipboardManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -50,11 +48,13 @@ import android.net.http.SslError;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.preference.PreferenceManager;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintManager;
+import android.provider.DocumentsContract;
 import android.text.Editable;
 import android.text.Spanned;
 import android.text.TextWatcher;
@@ -84,6 +84,7 @@ import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.webkit.WebViewDatabase;
 import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
 import android.widget.CursorAdapter;
 import android.widget.EditText;
 import android.widget.FrameLayout;
@@ -102,13 +103,11 @@ import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.app.AppCompatDelegate;
 import androidx.appcompat.widget.Toolbar;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.FileProvider;
 import androidx.core.content.res.ResourcesCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import androidx.viewpager.widget.ViewPager;
 import androidx.webkit.WebSettingsCompat;
@@ -128,6 +127,7 @@ import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
+import com.stoutner.privacybrowser.definitions.PendingDialog;
 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
@@ -140,7 +140,6 @@ import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
 import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
-import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
@@ -149,21 +148,28 @@ import com.stoutner.privacybrowser.helpers.AdHelper;
 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
-import com.stoutner.privacybrowser.helpers.FileNameHelper;
 import com.stoutner.privacybrowser.helpers.ProxyHelper;
 import com.stoutner.privacybrowser.views.NestedScrollWebView;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+
 import java.text.NumberFormat;
+
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -176,8 +182,8 @@ import java.util.concurrent.Executors;
 
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
         EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
-        PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
-        UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
+        PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, UrlHistoryDialog.NavigateHistoryListener,
+        WebViewTabFragment.NewTabListener {
 
     // The executor service handles background tasks.  It is accessed from `ViewSourceActivity`.
     public static ExecutorService executorService = Executors.newFixedThreadPool(4);
@@ -191,6 +197,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
     public static boolean restartFromBookmarksActivity;
 
+    // Define the public static variables.
+    public static ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
+
     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     public static String currentBookmarksFolder;
@@ -203,10 +212,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
 
-    // Start activity for result request codes.  The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
-    public final static int BROWSE_OPEN_REQUEST_CODE = 0;
-    public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1;
-    private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2;
+    // Define the start activity for result request codes.  The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
+    private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
+    public final static int BROWSE_OPEN_REQUEST_CODE = 1;
+    public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 2;
 
     // The proxy mode is public static so it can be accessed from `ProxyHelper()`.
     // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
@@ -239,9 +248,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
     private String searchURL;
 
-    // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
-    private Menu optionsMenu;
-
     // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
     private ArrayList<List<String[]>> easyList;
     private ArrayList<List<String[]>> easyPrivacy;
@@ -250,40 +256,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private ArrayList<List<String[]>> ultraList;
     private ArrayList<List<String[]>> ultraPrivacy;
 
-    // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
-    private String webViewDefaultUserAgent;
-
-    // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
-    private boolean incognitoModeEnabled;
-
-    // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
-    private boolean fullScreenBrowsingModeEnabled;
-
-    // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
-    private boolean inFullScreenBrowsingMode;
-
-    // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`.
-    private boolean hideAppBar;
-    private boolean scrollAppBar;
-
-    // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
-    private boolean loadingNewIntent;
-
-    // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
-    private boolean reapplyDomainSettingsOnRestart;
-
-    // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
-    private boolean reapplyAppSettingsOnRestart;
-
-    // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
-    private boolean displayingFullScreenVideo;
-
-    // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
-    private BroadcastReceiver orbotStatusBroadcastReceiver;
-
-    // The waiting for proxy boolean is used in `onResume()`, `initializeApp()` and `applyProxy()`.
-    private boolean waitingForProxy = false;
-
     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
     private ActionBarDrawerToggle actionBarDrawerToggle;
 
@@ -318,20 +290,91 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private boolean sanitizeFacebookClickIds;
     private boolean sanitizeTwitterAmpRedirects;
 
-    // The file path strings are used in `onSaveWebpage()` and `onRequestPermissionResult()`
-    private String openFilePath;
-    private String saveWebpageUrl;
-    private String saveWebpageFilePath;
+    // Declare the class variables
+    private BroadcastReceiver orbotStatusBroadcastReceiver;
+    private String webViewDefaultUserAgent;
+    private boolean incognitoModeEnabled;
+    private boolean fullScreenBrowsingModeEnabled;
+    private boolean inFullScreenBrowsingMode;
+    private boolean downloadWithExternalApp;
+    private boolean hideAppBar;
+    private boolean scrollAppBar;
+    private boolean bottomAppBar;
+    private boolean loadingNewIntent;
+    private boolean reapplyDomainSettingsOnRestart;
+    private boolean reapplyAppSettingsOnRestart;
+    private boolean displayingFullScreenVideo;
+    private boolean waitingForProxy;
+
+    // Define the class variables.
+    private long lastScrollUpdate = 0;
 
     // Declare the class views.
+    private FrameLayout rootFrameLayout;
     private DrawerLayout drawerLayout;
+    private RelativeLayout mainContentRelativeLayout;
     private AppBarLayout appBarLayout;
     private Toolbar toolbar;
+    private RelativeLayout urlRelativeLayout;
+    private EditText urlEditText;
+    private ActionBar actionBar;
     private LinearLayout findOnPageLinearLayout;
     private LinearLayout tabsLinearLayout;
     private TabLayout tabLayout;
     private SwipeRefreshLayout swipeRefreshLayout;
     private ViewPager webViewPager;
+    private FrameLayout fullScreenVideoFrameLayout;
+
+    // Declare the class menus.
+    private Menu optionsMenu;
+
+    // Declare the class menu items.
+    private MenuItem navigationBackMenuItem;
+    private MenuItem navigationForwardMenuItem;
+    private MenuItem navigationHistoryMenuItem;
+    private MenuItem navigationRequestsMenuItem;
+    private MenuItem optionsPrivacyMenuItem;
+    private MenuItem optionsRefreshMenuItem;
+    private MenuItem optionsCookiesMenuItem;
+    private MenuItem optionsDomStorageMenuItem;
+    private MenuItem optionsSaveFormDataMenuItem;
+    private MenuItem optionsClearDataMenuItem;
+    private MenuItem optionsClearCookiesMenuItem;
+    private MenuItem optionsClearDomStorageMenuItem;
+    private MenuItem optionsClearFormDataMenuItem;
+    private MenuItem optionsBlocklistsMenuItem;
+    private MenuItem optionsEasyListMenuItem;
+    private MenuItem optionsEasyPrivacyMenuItem;
+    private MenuItem optionsFanboysAnnoyanceListMenuItem;
+    private MenuItem optionsFanboysSocialBlockingListMenuItem;
+    private MenuItem optionsUltraListMenuItem;
+    private MenuItem optionsUltraPrivacyMenuItem;
+    private MenuItem optionsBlockAllThirdPartyRequestsMenuItem;
+    private MenuItem optionsProxyMenuItem;
+    private MenuItem optionsProxyNoneMenuItem;
+    private MenuItem optionsProxyTorMenuItem;
+    private MenuItem optionsProxyI2pMenuItem;
+    private MenuItem optionsProxyCustomMenuItem;
+    private MenuItem optionsUserAgentMenuItem;
+    private MenuItem optionsUserAgentPrivacyBrowserMenuItem;
+    private MenuItem optionsUserAgentWebViewDefaultMenuItem;
+    private MenuItem optionsUserAgentFirefoxOnAndroidMenuItem;
+    private MenuItem optionsUserAgentChromeOnAndroidMenuItem;
+    private MenuItem optionsUserAgentSafariOnIosMenuItem;
+    private MenuItem optionsUserAgentFirefoxOnLinuxMenuItem;
+    private MenuItem optionsUserAgentChromiumOnLinuxMenuItem;
+    private MenuItem optionsUserAgentFirefoxOnWindowsMenuItem;
+    private MenuItem optionsUserAgentChromeOnWindowsMenuItem;
+    private MenuItem optionsUserAgentEdgeOnWindowsMenuItem;
+    private MenuItem optionsUserAgentInternetExplorerOnWindowsMenuItem;
+    private MenuItem optionsUserAgentSafariOnMacosMenuItem;
+    private MenuItem optionsUserAgentCustomMenuItem;
+    private MenuItem optionsSwipeToRefreshMenuItem;
+    private MenuItem optionsWideViewportMenuItem;
+    private MenuItem optionsDisplayImagesMenuItem;
+    private MenuItem optionsDarkWebViewMenuItem;
+    private MenuItem optionsFontSizeMenuItem;
+    private MenuItem optionsAddOrEditDomainMenuItem;
 
     @Override
     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
@@ -355,9 +398,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Get the screenshot preference.
+        // Get the preferences.
         String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
         boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
+        bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
 
         // Get the theme entry values string array.
         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
@@ -393,10 +437,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         setTheme(R.style.PrivacyBrowser);
 
         // Set the content view.
-        setContentView(R.layout.main_framelayout);
+        if (bottomAppBar) {
+            setContentView(R.layout.main_framelayout_bottom_appbar);
+        } else {
+            setContentView(R.layout.main_framelayout_top_appbar);
+        }
 
         // Get handles for the views.
+        rootFrameLayout = findViewById(R.id.root_framelayout);
         drawerLayout = findViewById(R.id.drawerlayout);
+        mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
         appBarLayout = findViewById(R.id.appbar_layout);
         toolbar = findViewById(R.id.toolbar);
         findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
@@ -404,6 +454,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         tabLayout = findViewById(R.id.tablayout);
         swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
         webViewPager = findViewById(R.id.webviewpager);
+        fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
+
+        // Get a handle for the navigation view.
+        NavigationView navigationView = findViewById(R.id.navigationview);
+
+        // Get a handle for the navigation menu.
+        Menu navigationMenu = navigationView.getMenu();
+
+        // Get handles for the navigation menu items.
+        navigationBackMenuItem = navigationMenu.findItem(R.id.back);
+        navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
+        navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
+        navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
+
+        // Listen for touches on the navigation menu.
+        navigationView.setNavigationItemSelectedListener(this);
 
         // Get a handle for the app compat delegate.
         AppCompatDelegate appCompatDelegate = getDelegate();
@@ -412,15 +478,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         appCompatDelegate.setSupportActionBar(toolbar);
 
         // Get a handle for the action bar.
-        ActionBar actionBar = appCompatDelegate.getSupportActionBar();
+        actionBar = appCompatDelegate.getSupportActionBar();
 
-        // This is needed to get rid of the Android Studio warning that the action bar might be null.
+        // Remove the incorrect lint warning below that the action bar might be null.
         assert actionBar != null;
 
         // Add the custom layout, which shows the URL text bar.
         actionBar.setCustomView(R.layout.url_app_bar);
         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
+        // Get handles for the views in the URL app bar.
+        urlRelativeLayout = findViewById(R.id.url_relativelayout);
+        urlEditText = findViewById(R.id.url_edittext);
+
         // Create the hamburger icon at the start of the AppBar.
         actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
 
@@ -466,6 +536,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // 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.
             if (intentUriData != null || intentStringExtra != null || isWebSearch) {
+                // Exit the full screen video if it is displayed.
+                if (displayingFullScreenVideo) {
+                    // Exit full screen video mode.
+                    exitFullScreenVideo();
+
+                    // Reload the current WebView.  Otherwise, it can display entirely black.
+                    currentWebView.reload();
+                }
+
                 // Get the shared preferences.
                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
@@ -578,11 +657,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         updatePrivacyIcons(true);
     }
 
-    // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
+    // `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.
     @Override
-    public void onResume() {
+    public void onStart() {
         // Run the default commands.
-        super.onResume();
+        super.onStart();
 
         // Resume any WebViews.
         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
@@ -614,9 +693,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Reapply any system UI flags and the ad in the free flavor.
         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {  // The system is displaying a website or a video in full screen mode.
-            // Get a handle for the root frame layouts.
-            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-
             /* Hide the system bars.
              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
@@ -626,15 +702,31 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
         } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // The system in not in full screen mode.
+            // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+            View adView = findViewById(R.id.adview);
+
             // Resume the ad.
-            AdHelper.resumeAd(findViewById(R.id.adview));
+            AdHelper.resumeAd(adView);
+        }
+
+        // Show any pending dialogs.
+        for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
+            // Get the pending dialog from the array list.
+            PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
+
+            // Show the pending dialog.
+            pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
         }
+
+        // Clear the pending dialogs array list.
+        pendingDialogsArrayList.clear();
     }
 
+    // `onStop()` runs after `onPause()`.  It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
     @Override
-    public void onPause() {
+    public void onStop() {
         // Run the default commands.
-        super.onPause();
+        super.onStop();
 
         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
             // Get the WebView tab fragment.
@@ -660,8 +752,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Pause the ad or it will continue to consume resources in the background on the free flavor.
         if (BuildConfig.FLAVOR.contentEquals("free")) {
+            // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+            View adView = findViewById(R.id.adview);
+
             // Pause the ad.
-            AdHelper.pauseAd(findViewById(R.id.adview));
+            AdHelper.pauseAd(adView);
         }
     }
 
@@ -743,31 +838,66 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
         optionsMenu = menu;
 
-        // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
-        updatePrivacyIcons(false);
-
-        // Get handles for the menu items.
+        // Get handles for the class menu items.
+        optionsPrivacyMenuItem = menu.findItem(R.id.javascript);
+        optionsRefreshMenuItem = menu.findItem(R.id.refresh);
+        optionsCookiesMenuItem = menu.findItem(R.id.cookies);
+        optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage);
+        optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data);  // Form data can be removed once the minimum API >= 26.
+        optionsClearDataMenuItem = menu.findItem(R.id.clear_data);
+        optionsClearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
+        optionsClearDomStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
+        optionsClearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
+        optionsBlocklistsMenuItem = menu.findItem(R.id.blocklists);
+        optionsEasyListMenuItem = menu.findItem(R.id.easylist);
+        optionsEasyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
+        optionsFanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
+        optionsFanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
+        optionsUltraListMenuItem = menu.findItem(R.id.ultralist);
+        optionsUltraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
+        optionsBlockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
+        optionsProxyMenuItem = menu.findItem(R.id.proxy);
+        optionsProxyNoneMenuItem = menu.findItem(R.id.proxy_none);
+        optionsProxyTorMenuItem = menu.findItem(R.id.proxy_tor);
+        optionsProxyI2pMenuItem = menu.findItem(R.id.proxy_i2p);
+        optionsProxyCustomMenuItem = menu.findItem(R.id.proxy_custom);
+        optionsUserAgentMenuItem = menu.findItem(R.id.user_agent);
+        optionsUserAgentPrivacyBrowserMenuItem = menu.findItem(R.id.user_agent_privacy_browser);
+        optionsUserAgentWebViewDefaultMenuItem = menu.findItem(R.id.user_agent_webview_default);
+        optionsUserAgentFirefoxOnAndroidMenuItem = menu.findItem(R.id.user_agent_firefox_on_android);
+        optionsUserAgentChromeOnAndroidMenuItem = menu.findItem(R.id.user_agent_chrome_on_android);
+        optionsUserAgentSafariOnIosMenuItem = menu.findItem(R.id.user_agent_safari_on_ios);
+        optionsUserAgentFirefoxOnLinuxMenuItem = menu.findItem(R.id.user_agent_firefox_on_linux);
+        optionsUserAgentChromiumOnLinuxMenuItem = menu.findItem(R.id.user_agent_chromium_on_linux);
+        optionsUserAgentFirefoxOnWindowsMenuItem = menu.findItem(R.id.user_agent_firefox_on_windows);
+        optionsUserAgentChromeOnWindowsMenuItem = menu.findItem(R.id.user_agent_chrome_on_windows);
+        optionsUserAgentEdgeOnWindowsMenuItem = menu.findItem(R.id.user_agent_edge_on_windows);
+        optionsUserAgentInternetExplorerOnWindowsMenuItem = menu.findItem(R.id.user_agent_internet_explorer_on_windows);
+        optionsUserAgentSafariOnMacosMenuItem = menu.findItem(R.id.user_agent_safari_on_macos);
+        optionsUserAgentCustomMenuItem = menu.findItem(R.id.user_agent_custom);
+        optionsSwipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
+        optionsWideViewportMenuItem = menu.findItem(R.id.wide_viewport);
+        optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images);
+        optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview);
+        optionsFontSizeMenuItem = menu.findItem(R.id.font_size);
+        optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain);
+
+        // Get handles for the method menu items.
         MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks);
-        MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
-        MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
-        MenuItem darkWebViewMenuItem = menu.findItem(R.id.dark_webview);
         MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
 
-        // Only display third-party cookies if API >= 21
-        toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
+        // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
+        updatePrivacyIcons(false);
 
         // Only display the form data menu items if the API < 26.
-        toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
-        clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+        optionsSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+        optionsClearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
 
         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
-        clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
+        optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
 
         // Only display the dark WebView menu item if API >= 21.
-        darkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
+        optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
 
         // Only show Ad Consent if this is the free flavor.
         adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
@@ -780,19 +910,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // 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.
         if (displayAdditionalAppBarIcons) {
-            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+            optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
             bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
         } else { //Do not display the additional icons.
-            refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
             bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-            toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
         }
 
         // Replace Refresh with Stop if a URL is already loading.
         if (currentWebView != null && currentWebView.getProgress() != 100) {
             // Set the title.
-            refreshMenuItem.setTitle(R.string.stop);
+            optionsRefreshMenuItem.setTitle(R.string.stop);
 
             // Set the icon if it is displayed in the app bar.
             if (displayAdditionalAppBarIcons) {
@@ -801,9 +931,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Set the icon according to the current theme status.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    refreshMenuItem.setIcon(R.drawable.close_blue_day);
+                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
                 } else {
-                    refreshMenuItem.setIcon(R.drawable.close_blue_night);
+                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
                 }
             }
         }
@@ -814,32 +944,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
-        // Get handles for the menu items.
-        MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
-        MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
-        MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
-        MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
-        MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
-        MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
-        MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
-        MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
-        MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
-        MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
-        MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
-        MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
-        MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
-        MenuItem ultraListMenuItem = menu.findItem(R.id.ultralist);
-        MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
-        MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
-        MenuItem proxyMenuItem = menu.findItem(R.id.proxy);
-        MenuItem userAgentMenuItem = menu.findItem(R.id.user_agent);
-        MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
-        MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
-        MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
-        MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
-        MenuItem darkWebViewMenuItem = menu.findItem(R.id.dark_webview);
-
         // Get a handle for the cookie manager.
         CookieManager cookieManager = CookieManager.getInstance();
 
@@ -851,9 +955,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         if (currentWebView != null) {
             // Set the add or edit domain text.
             if (currentWebView.getDomainSettingsApplied()) {
-                addOrEditDomain.setTitle(R.string.edit_domain_settings);
+                optionsAddOrEditDomainMenuItem.setTitle(R.string.edit_domain_settings);
             } else {
-                addOrEditDomain.setTitle(R.string.add_domain_settings);
+                optionsAddOrEditDomainMenuItem.setTitle(R.string.add_domain_settings);
             }
 
             // Get the current user agent from the WebView.
@@ -863,52 +967,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             fontSize = currentWebView.getSettings().getTextZoom();
 
             // Set the status of the menu item checkboxes.
-            domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
-            saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
-            easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
-            easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
-            fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
-            fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
-            ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
-            ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
-            blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
-            swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
-            wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
-            displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
+            optionsDomStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
+            optionsSaveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
+            optionsEasyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+            optionsEasyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+            optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+            optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+            optionsUltraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+            optionsUltraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+            optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+            optionsSwipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
+            optionsWideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
+            optionsDisplayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
 
             // Initialize the display names for the blocklists with the number of blocked requests.
-            blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-            easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
-            easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
-            fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
-            fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
-            ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
-            ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
-            blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
-
-            // Only modify third-party cookies if the API >= 21.
-            if (Build.VERSION.SDK_INT >= 21) {
-                // Set the status of the third-party cookies checkbox.
-                thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
-
-                // Enable third-party cookies if first-party cookies are enabled.
-                thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
-            }
+            optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+            optionsEasyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
+            optionsEasyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
+            optionsFanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
+            optionsFanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
+            optionsUltraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
+            optionsUltraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
+            optionsBlockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
 
             // Enable DOM Storage if JavaScript is enabled.
-            domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
+            optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
 
             // Set the checkbox status for dark WebView if the WebView supports it.
             if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
-                darkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
+                optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
             }
         }
 
-        // Set the checked status of the first party cookies menu item.
-        firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
+        // Set the cookies menu item checked status.
+        optionsCookiesMenuItem.setChecked(cookieManager.acceptCookie());
 
         // Enable Clear Cookies if there are any.
-        clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
+        optionsClearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
 
         // 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`.
         String privateDataDirectoryString = getApplicationInfo().dataDir;
@@ -930,7 +1025,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Enable Clear DOM Storage if there is any.
-        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
+        optionsClearDomStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
 
         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
         if (Build.VERSION.SDK_INT < 26) {
@@ -938,133 +1033,133 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
 
             // Enable the clear form data menu item if there is anything to clear.
-            clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
+            optionsClearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
         }
 
         // Enable Clear Data if any of the submenu items are enabled.
-        clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
+        optionsClearDataMenuItem.setEnabled(optionsClearCookiesMenuItem.isEnabled() || optionsClearDomStorageMenuItem.isEnabled() || optionsClearFormDataMenuItem.isEnabled());
 
         // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
-        fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
+        optionsFanboysSocialBlockingListMenuItem.setEnabled(!optionsFanboysAnnoyanceListMenuItem.isChecked());
 
         // Set the proxy title and check the applied proxy.
         switch (proxyMode) {
             case ProxyHelper.NONE:
                 // Set the proxy title.
-                proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
+                optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
 
                 // Check the proxy None radio button.
-                menu.findItem(R.id.proxy_none).setChecked(true);
+                optionsProxyNoneMenuItem.setChecked(true);
                 break;
 
             case ProxyHelper.TOR:
                 // Set the proxy title.
-                proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
+                optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
 
                 // Check the proxy Tor radio button.
-                menu.findItem(R.id.proxy_tor).setChecked(true);
+                optionsProxyTorMenuItem.setChecked(true);
                 break;
 
             case ProxyHelper.I2P:
                 // Set the proxy title.
-                proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
+                optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
 
                 // Check the proxy I2P radio button.
-                menu.findItem(R.id.proxy_i2p).setChecked(true);
+                optionsProxyI2pMenuItem.setChecked(true);
                 break;
 
             case ProxyHelper.CUSTOM:
                 // Set the proxy title.
-                proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
+                optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
 
                 // Check the proxy Custom radio button.
-                menu.findItem(R.id.proxy_custom).setChecked(true);
+                optionsProxyCustomMenuItem.setChecked(true);
                 break;
         }
 
         // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
         if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
 
             // Select the Privacy Browser radio box.
-            menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
+            optionsUserAgentPrivacyBrowserMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
 
             // Select the WebView Default radio box.
-            menu.findItem(R.id.user_agent_webview_default).setChecked(true);
+            optionsUserAgentWebViewDefaultMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
 
             // Select the Firefox on Android radio box.
-            menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
+            optionsUserAgentFirefoxOnAndroidMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
 
             // Select the Chrome on Android radio box.
-            menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
+            optionsUserAgentChromeOnAndroidMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
 
             // Select the Safari on iOS radio box.
-            menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
+            optionsUserAgentSafariOnIosMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
 
             // Select the Firefox on Linux radio box.
-            menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
+            optionsUserAgentFirefoxOnLinuxMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
 
             // Select the Chromium on Linux radio box.
-            menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
+            optionsUserAgentChromiumOnLinuxMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
 
             // Select the Firefox on Windows radio box.
-            menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
+            optionsUserAgentFirefoxOnWindowsMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
 
             // Select the Chrome on Windows radio box.
-            menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
+            optionsUserAgentChromeOnWindowsMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
 
             // Select the Edge on Windows radio box.
-            menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
+            optionsUserAgentEdgeOnWindowsMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
 
             // Select the Internet on Windows radio box.
-            menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
+            optionsUserAgentInternetExplorerOnWindowsMenuItem.setChecked(true);
         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
 
             // Select the Safari on macOS radio box.
-            menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
+            optionsUserAgentSafariOnMacosMenuItem.setChecked(true);
         } else {  // Custom user agent.
             // Update the user agent menu item title.
-            userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
+            optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
 
             // Select the Custom radio box.
-            menu.findItem(R.id.user_agent_custom).setChecked(true);
+            optionsUserAgentCustomMenuItem.setChecked(true);
         }
 
         // Set the font size title.
-        fontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
+        optionsFontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
 
         // Run all the other default commands.
         super.onPrepareOptionsMenu(menu);
@@ -1085,7 +1180,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         int menuItemId = menuItem.getItemId();
 
         // Run the commands that correlate to the selected menu item.
-        if (menuItemId == R.id.toggle_javascript) {  // JavaScript.
+        if (menuItemId == R.id.javascript) {  // JavaScript.
             // Toggle the JavaScript status.
             currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
 
@@ -1124,12 +1219,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Consume the event.
             return true;
-        } else if (menuItemId == R.id.toggle_first_party_cookies) {  // First-party cookies.
+        } else if (menuItemId == R.id.cookies) {  // Cookies.
             // Switch the first-party cookie status.
             cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
 
-            // Store the first-party cookie status.
-            currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
+            // Store the cookie status.
+            currentWebView.setAcceptCookies(cookieManager.acceptCookie());
 
             // Update the menu checkbox.
             menuItem.setChecked(cookieManager.acceptCookie());
@@ -1138,10 +1233,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             updatePrivacyIcons(true);
 
             // Display a snackbar.
-            if (cookieManager.acceptCookie()) {  // First-party cookies are enabled.
-                Snackbar.make(webViewPager, R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+            if (cookieManager.acceptCookie()) {  // Cookies are enabled.
+                Snackbar.make(webViewPager, R.string.cookies_enabled, Snackbar.LENGTH_SHORT).show();
             } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
-                Snackbar.make(webViewPager, R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+                Snackbar.make(webViewPager, R.string.cookies_disabled, Snackbar.LENGTH_SHORT).show();
             } else {  // Privacy mode.
                 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
             }
@@ -1151,29 +1246,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Consume the event.
             return true;
-        } else if (menuItemId == R.id.toggle_third_party_cookies) {  // Third-party cookies.
-            // Only act if the API >= 21.  Otherwise, there are no third-party cookie controls.
-            if (Build.VERSION.SDK_INT >= 21) {
-                // Toggle the status of thirdPartyCookiesEnabled.
-                cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
-
-                // Update the menu checkbox.
-                menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
-
-                // Display a snackbar.
-                if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
-                    Snackbar.make(webViewPager, R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                } else {
-                    Snackbar.make(webViewPager, R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
-                }
-
-                // Reload the current WebView.
-                currentWebView.reload();
-            }
-
-            // Consume the event.
-            return true;
-        } else if (menuItemId == R.id.toggle_dom_storage) {  // DOM storage.
+        } else if (menuItemId == R.id.dom_storage) {  // DOM storage.
             // Toggle the status of domStorageEnabled.
             currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
 
@@ -1195,7 +1268,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Consume the event.
             return true;
-        } else if (menuItemId == R.id.toggle_save_form_data) {  // Form data.  This can be removed once the minimum API >= 26.
+        } else if (menuItemId == R.id.save_form_data) {  // Form data.  This can be removed once the minimum API >= 26.
             // Switch the status of saveFormDataEnabled.
             currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
 
@@ -1349,11 +1422,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Update the menu checkbox.
             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
-            // Get a handle for the Fanboy's Social Block List menu item.
-            MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
-
-            // Update the staus of Fanboy's Social Blocking List.
-            fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+            // Update the status of Fanboy's Social Blocking List.
+            optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1574,13 +1644,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Toggle the stored status of swipe to refresh.
             currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
 
-            // Get a handle for the swipe refresh layout.
-            SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-
             // Update the swipe refresh layout.
             if (currentWebView.getSwipeToRefresh()) {  // 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.
-                swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+                swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
             } else {  // Swipe to refresh is disabled.
                 // Disable the swipe refresh layout.
                 swipeRefreshLayout.setEnabled(false);
@@ -1668,30 +1735,34 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
 
             // Print the document.
-            printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+            printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
 
             // Consume the event.
             return true;
         } else if (menuItemId == R.id.save_url) {  // Save URL.
-            // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-            new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                    currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
+            // Check the download preference.
+            if (downloadWithExternalApp) {  // Download with an external app.
+                downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
+            } else {  // Handle the download inside of Privacy Browser.
+                // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
+            }
 
             // Consume the event.
             return true;
-        } else if (menuItemId == R.id.save_archive) {  // Save archive.
+        } else if (menuItemId == R.id.save_archive) {
             // Instantiate the save dialog.
-            DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE, null, null, getString(R.string.webpage_mht), null,
+            DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_ARCHIVE, currentWebView.getCurrentUrl(), null, null, null,
                     false);
 
             // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
             saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
 
-            // Consume the event.
             return true;
         } else if (menuItemId == R.id.save_image) {  // Save image.
             // Instantiate the save dialog.
-            DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE, null, null, getString(R.string.webpage_png), null,
+            DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, currentWebView.getCurrentUrl(), null, null, null,
                     false);
 
             // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
@@ -1714,8 +1785,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
 
             // Add the variables to the intent.
-            viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
-            viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
+            viewSourceIntent.putExtra(ViewSourceActivityKt.CURRENT_URL, currentWebView.getUrl());
+            viewSourceIntent.putExtra(ViewSourceActivityKt.USER_AGENT, currentWebView.getSettings().getUserAgentString());
 
             // Make it so.
             startActivity(viewSourceIntent);
@@ -1949,14 +2020,50 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Make it so.
             startActivity(requestsIntent);
         } else if (menuItemId == R.id.downloads) {  // Downloads.
-            // Launch the system Download Manager.
-            Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+            // Try the default system download manager.
+            try {
+                // Launch the default system Download Manager.
+                Intent defaultDownloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
 
-            // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
-            downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                // Launch as a new task so that the download manager and Privacy Browser show as separate windows in the recent tasks list.
+                defaultDownloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-            // Make it so.
-            startActivity(downloadManagerIntent);
+                // Make it so.
+                startActivity(defaultDownloadManagerIntent);
+            } catch (Exception defaultDownloadManagerException) {
+                // Try a generic file manager.
+                try {
+                    // Create a generic file manager intent.
+                    Intent genericFileManagerIntent = new Intent(Intent.ACTION_VIEW);
+
+                    // Open the download directory.
+                    genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR);
+
+                    // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
+                    genericFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                    // Make it so.
+                    startActivity(genericFileManagerIntent);
+                } catch (Exception genericFileManagerException) {
+                    // Try an alternate file manager.
+                    try {
+                        // Create an alternate file manager intent.
+                        Intent alternateFileManagerIntent = new Intent(Intent.ACTION_VIEW);
+
+                        // Open the download directory.
+                        alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder");
+
+                        // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
+                        alternateFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                        // Open the alternate file manager.
+                        startActivity(alternateFileManagerIntent);
+                    } catch (Exception alternateFileManagerException) {
+                        // Display a snackbar.
+                        Snackbar.make(currentWebView, R.string.no_file_manager_detected, Snackbar.LENGTH_INDEFINITE).show();
+                    }
+                }
+            }
         } else if (menuItemId == R.id.domains) {  // Domains.
             // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
             reapplyDomainSettingsOnRestart = true;
@@ -2038,7 +2145,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
 
             // Add the blocklist versions to the intent.
-            aboutIntent.putExtra("blocklist_versions", blocklistVersions);
+            aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions);
 
             // Make it so.
             startActivity(aboutIntent);
@@ -2065,8 +2172,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Reload the ad for the free flavor if not in full screen mode.
         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+            // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+            View adView = findViewById(R.id.adview);
+
             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+            // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
+            AdHelper.loadAd(adView, getApplicationContext(), this, getString(R.string.ad_unit_id));
         }
 
         // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
@@ -2147,9 +2258,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save URL entry.
                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
+                    // Check the download preference.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        downloadUrlWithExternalApp(linkUrl);
+                    } else {  // Handle the download inside of Privacy Browser.
+                        // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                    }
 
                     // Consume the event.
                     return true;
@@ -2214,9 +2330,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save Image entry.
                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
-                   // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
+                    // Check the download preference.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        downloadUrlWithExternalApp(imageUrl);
+                    } else {  // Handle the download inside of Privacy Browser.
+                        // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                    }
 
                     // Consume the event.
                     return true;
@@ -2314,9 +2435,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save Image entry.
                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
+                    // Check the download preference.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        downloadUrlWithExternalApp(imageUrl);
+                    } else {  // Handle the download inside of Privacy Browser.
+                        // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                    }
 
                     // Consume the event.
                     return true;
@@ -2336,9 +2462,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save URL entry.
                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
+                    // Check the download preference.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        downloadUrlWithExternalApp(linkUrl);
+                    } else {  // Handle the download inside of Privacy Browser.
+                        // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                    }
 
                     // Consume the event.
                     return true;
@@ -2452,20 +2583,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         assert dialog != null;
 
         // Get handles for the views in the dialog fragment.
-        EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
-        RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
-        ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
+        EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
+        RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
+        ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
 
         // Get new folder name string.
-        String folderNameString = createFolderNameEditText.getText().toString();
+        String folderNameString = folderNameEditText.getText().toString();
 
         // Create a folder icon bitmap.
         Bitmap folderIconBitmap;
 
         // Set the folder icon bitmap according to the dialog.
-        if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
+        if (defaultIconRadioButton.isChecked()) {  // Use the default folder icon.
             // Get the default folder icon drawable.
-            Drawable folderIconDrawable = folderIconImageView.getDrawable();
+            Drawable folderIconDrawable = defaultIconImageView.getDrawable();
 
             // Convert the folder icon drawable to a bitmap drawable.
             BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
@@ -2506,7 +2637,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
-    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
+    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
         // Remove the incorrect lint warning below that the dialog fragment might be null.
         assert dialogFragment != null;
 
@@ -2517,10 +2648,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         assert dialog != null;
 
         // Get handles for the views from the dialog.
-        EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
-        RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
-        RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
-        ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
+        RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
+        RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
+        ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
+        EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
 
         // Get the new folder name.
         String newFolderNameString = editFolderNameEditText.getText().toString();
@@ -2560,7 +2691,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Update the folder icon in the database.
             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
         } else {  // The folder icon and the name have changed.
-            // Get the new folder icon `Bitmap`.
+            // Get the new folder icon bitmap.
             Bitmap folderIconBitmap;
             if (defaultFolderIconRadioButton.isChecked()) {
                 // Get the default folder icon drawable.
@@ -2607,60 +2738,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // close the bookmarks drawer.
             drawerLayout.closeDrawer(GravityCompat.END);
         } else if (displayingFullScreenVideo) {  // A full screen video is shown.
-            // Get a handle for the layouts.
-            FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-            RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
-            FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
-
-            // Re-enable the screen timeout.
-            fullScreenVideoFrameLayout.setKeepScreenOn(false);
-
-            // Unset the full screen video flag.
-            displayingFullScreenVideo = false;
-
-            // Remove all the views from the full screen video frame layout.
-            fullScreenVideoFrameLayout.removeAllViews();
-
-            // Hide the full screen video frame layout.
-            fullScreenVideoFrameLayout.setVisibility(View.GONE);
-
-            // Enable the sliding drawers.
-            drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
-            // Show the main content relative layout.
-            mainContentRelativeLayout.setVisibility(View.VISIBLE);
-
-            // Apply the appropriate full screen mode flags.
-            if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-                // Hide the banner ad in the free flavor.
-                if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    AdHelper.hideAd(findViewById(R.id.adview));
-                }
-
-                /* Hide the system bars.
-                 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                 */
-                rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
-                // Reload the website if the app bar is hidden.  Otherwise, there is some bug in Android that causes the WebView to be entirely black.
-                if (hideAppBar) {
-                    // Reload the WebView.
-                    currentWebView.reload();
-                }
-            } else {  // Switch to normal viewing mode.
-                // Remove the `SYSTEM_UI` flags from the root frame layout.
-                rootFrameLayout.setSystemUiVisibility(0);
-            }
-
-            // Reload the ad for the free flavor if not in full screen mode.
-            if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-                // Reload the ad.
-                AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
-            }
+            // Exit the full screen video.
+            exitFullScreenVideo();
         } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
             // Get the current web back forward list.
             WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
@@ -2705,76 +2784,66 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
                 break;
 
-            case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
+            case BROWSE_OPEN_REQUEST_CODE:
                 // Don't do anything if the user pressed back from the file picker.
                 if (resultCode == Activity.RESULT_OK) {
-                    // Get a handle for the save dialog fragment.
-                    DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
+                    // Get a handle for the open dialog fragment.
+                    DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
 
                     // Only update the file name if the dialog still exists.
-                    if (saveWebpageDialogFragment != null) {
-                        // Get a handle for the save webpage dialog.
-                        Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
+                    if (openDialogFragment != null) {
+                        // Get a handle for the open dialog.
+                        Dialog openDialog = openDialogFragment.getDialog();
 
                         // Remove the incorrect lint warning below that the dialog might be null.
-                        assert saveWebpageDialog != null;
+                        assert openDialog != null;
 
                         // Get a handle for the file name edit text.
-                        EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
-                        TextView fileExistsWarningTextView = saveWebpageDialog.findViewById(R.id.file_exists_warning_textview);
-
-                        // Instantiate the file name helper.
-                        FileNameHelper fileNameHelper = new FileNameHelper();
+                        EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
 
-                        // Get the file path if it isn't null.
-                        if (returnedIntent.getData() != null) {
-                            // Convert the file name URI to a file name path.
-                            String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
+                        // Get the file name URI from the intent.
+                        Uri fileNameUri = returnedIntent.getData();
 
-                            // Set the file name path as the text of the file name edit text.
-                            fileNameEditText.setText(fileNamePath);
+                        // Get the file name string from the URI.
+                        String fileNameString = fileNameUri.toString();
 
-                            // Move the cursor to the end of the file name edit text.
-                            fileNameEditText.setSelection(fileNamePath.length());
+                        // Set the file name text.
+                        fileNameEditText.setText(fileNameString);
 
-                            // Hide the file exists warning.
-                            fileExistsWarningTextView.setVisibility(View.GONE);
-                        }
+                        // Move the cursor to the end of the file name edit text.
+                        fileNameEditText.setSelection(fileNameString.length());
                     }
                 }
                 break;
 
-            case BROWSE_OPEN_REQUEST_CODE:
+            case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
                 // Don't do anything if the user pressed back from the file picker.
                 if (resultCode == Activity.RESULT_OK) {
-                    // Get a handle for the open dialog fragment.
-                    DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
+                    // Get a handle for the save dialog fragment.
+                    DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
 
                     // Only update the file name if the dialog still exists.
-                    if (openDialogFragment != null) {
-                        // Get a handle for the open dialog.
-                        Dialog openDialog = openDialogFragment.getDialog();
+                    if (saveWebpageDialogFragment != null) {
+                        // Get a handle for the save webpage dialog.
+                        Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
 
                         // Remove the incorrect lint warning below that the dialog might be null.
-                        assert openDialog != null;
+                        assert saveWebpageDialog != null;
 
                         // Get a handle for the file name edit text.
-                        EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
+                        EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
 
-                        // Instantiate the file name helper.
-                        FileNameHelper fileNameHelper = new FileNameHelper();
+                        // Get the file name URI from the intent.
+                        Uri fileNameUri = returnedIntent.getData();
 
-                        // Get the file path if it isn't null.
-                        if (returnedIntent.getData() != null) {
-                            // Convert the file name URI to a file name path.
-                            String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
+                        // Get the file name string from the URI.
+                        String fileNameString = fileNameUri.toString();
 
-                            // Set the file name path as the text of the file name edit text.
-                            fileNameEditText.setText(fileNamePath);
+                        // Set the file name text.
+                        fileNameEditText.setText(fileNameString);
 
-                            // Move the cursor to the end of the file name edit text.
-                            fileNameEditText.setSelection(fileNamePath.length());
-                        }
+                        // Move the cursor to the end of the file name edit text.
+                        fileNameEditText.setSelection(fileNameString.length());
                     }
                 }
                 break;
@@ -2782,9 +2851,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void loadUrlFromTextBox() {
-        // Get a handle for the URL edit text.
-        EditText urlEditText = findViewById(R.id.url_edittext);
-
         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
         String unformattedUrlString = urlEditText.getText().toString().trim();
 
@@ -2935,209 +3001,167 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Remove the incorrect lint warning below that the dialog might be null.
         assert dialog != null;
 
-        // Get a handle for the file name edit text.
+        // Get handles for the views.
         EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
+        CheckBox mhtCheckBox = dialog.findViewById(R.id.mht_checkbox);
 
         // Get the file path string.
-        openFilePath = fileNameEditText.getText().toString();
+        String openFilePath = fileNameEditText.getText().toString();
 
         // Apply the domain settings.  This resets the favorite icon and removes any domain settings.
-        applyDomainSettings(currentWebView, "file://" + openFilePath, true, false, false);
+        applyDomainSettings(currentWebView, openFilePath, true, false, false);
 
-        // Check to see if the storage permission is needed.
-        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // The storage permission has been granted.
-            // Open the file.
-            currentWebView.loadUrl("file://" + openFilePath);
-        } else {  // The storage permission has not been granted.
-            // Get the external private directory file.
-            File externalPrivateDirectoryFile = getExternalFilesDir(null);
-
-            // Remove the incorrect lint error below that the file might be null.
-            assert externalPrivateDirectoryFile != null;
-
-            // Get the external private directory string.
-            String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
-
-            // Check to see if the file path is in the external private directory.
-            if (openFilePath.startsWith(externalPrivateDirectory)) {  // the file path is in the external private directory.
-                // Open the file.
-                currentWebView.loadUrl("file://" + openFilePath);
-            } else {  // The file path is in a public directory.
-                // Check if the user has previously denied the storage permission.
-                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                    // Instantiate the storage permission alert dialog.
-                    DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.OPEN);
-
-                    // Show the storage permission alert dialog.  The permission will be requested the the dialog is closed.
-                    storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
-                } else {  // Show the permission request directly.
-                    // Request the write external storage permission.  The file will be opened when it finishes.
-                    ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, StoragePermissionDialog.OPEN);
+        // Open the file according to the type.
+        if (mhtCheckBox.isChecked()) {  // Force opening of an MHT file.
+            try {
+                // Get the MHT file input stream.
+                InputStream mhtFileInputStream = getContentResolver().openInputStream(Uri.parse(openFilePath));
+
+                // Create a temporary MHT file.
+                File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
+
+                // Get a file output stream for the temporary MHT file.
+                FileOutputStream temporaryMhtFileOutputStream = new FileOutputStream(temporaryMhtFile);
+
+                // Create a transfer byte array.
+                byte[] transferByteArray = new byte[1024];
+
+                // Create an integer to track the number of bytes read.
+                int bytesRead;
+
+                // Copy the temporary MHT file input stream to the MHT output stream.
+                while ((bytesRead = mhtFileInputStream.read(transferByteArray)) > 0) {
+                    temporaryMhtFileOutputStream.write(transferByteArray, 0, bytesRead);
                 }
+
+                // Flush the temporary MHT file output stream.
+                temporaryMhtFileOutputStream.flush();
+
+                // Close the streams.
+                temporaryMhtFileOutputStream.close();
+                mhtFileInputStream.close();
+
+                // Load the temporary MHT file.
+                currentWebView.loadUrl(temporaryMhtFile.toString());
+            } catch (Exception exception) {
+                // Display a snackbar.
+                Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
             }
+        } else {  // Let the WebView handle opening of the file.
+            // Open the file.
+            currentWebView.loadUrl(openFilePath);
         }
     }
 
-    @Override
-    public void onSaveWebpage(int saveType, String originalUrlString, DialogFragment dialogFragment) {
+    private void downloadUrlWithExternalApp(String url) {
+        // Create a download intent.  Not specifying the action type will display the maximum number of options.
+        Intent downloadIntent = new Intent();
+
+        // Set the URI and the mime type.
+        downloadIntent.setDataAndType(Uri.parse(url), "text/html");
+
+        // Flag the intent to open in a new task.
+        downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // Show the chooser.
+        startActivity(Intent.createChooser(downloadIntent, getString(R.string.download_with_external_app)));
+    }
+
+    public void onSaveWebpage(int saveType, @NonNull String originalUrlString, DialogFragment dialogFragment) {
         // Get the dialog.
         Dialog dialog = dialogFragment.getDialog();
 
         // Remove the incorrect lint warning below that the dialog might be null.
         assert dialog != null;
 
-        // Get a handle for the edit texts.
-        EditText urlEditText = dialog.findViewById(R.id.url_edittext);
+        // Get a handle for the file name edit text.
         EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
 
-        // Store the URL.
-        if ((originalUrlString != null) && originalUrlString.startsWith("data:")) {
-            // Save the original URL.
-            saveWebpageUrl = originalUrlString;
-        } else {
-            // Get the URL from the edit text, which may have been modified.
-            saveWebpageUrl = urlEditText.getText().toString();
-        }
-
         // Get the file path from the edit text.
-        saveWebpageFilePath = fileNameEditText.getText().toString();
-
-        // Check to see if the storage permission is needed.
-        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // The storage permission has been granted.
-            //Save the webpage according to the save type.
-            switch (saveType) {
-                case StoragePermissionDialog.SAVE_URL:
-                    // Save the URL.
-                    new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
-                    break;
-
-                case StoragePermissionDialog.SAVE_ARCHIVE:
-                    // Save the webpage archive.
-                    saveWebpageArchive(saveWebpageFilePath);
-                    break;
-
-                case StoragePermissionDialog.SAVE_IMAGE:
-                    // Save the webpage image.
-                    new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
-                    break;
-            }
+        String saveWebpageFilePath = fileNameEditText.getText().toString();
 
-            // Reset the strings.
-            saveWebpageUrl = "";
-            saveWebpageFilePath = "";
-        } else {  // The storage permission has not been granted.
-            // Get the external private directory file.
-            File externalPrivateDirectoryFile = getExternalFilesDir(null);
-
-            // Remove the incorrect lint error below that the file might be null.
-            assert externalPrivateDirectoryFile != null;
-
-            // Get the external private directory string.
-            String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
-
-            // Check to see if the file path is in the external private directory.
-            if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) {  // The file path is in the external private directory.
-                // Save the webpage according to the save type.
-                switch (saveType) {
-                    case StoragePermissionDialog.SAVE_URL:
-                        // Save the URL.
-                        new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
-                        break;
+        //Save the webpage according to the save type.
+        switch (saveType) {
+            case SaveWebpageDialog.SAVE_URL:
+                // Get a handle for the dialog URL edit text.
+                EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext);
 
-                    case StoragePermissionDialog.SAVE_ARCHIVE:
-                        // Save the webpage archive.
-                        saveWebpageArchive(saveWebpageFilePath);
-                        break;
+                // Define the save webpage URL.
+                String saveWebpageUrl;
 
-                    case StoragePermissionDialog.SAVE_IMAGE:
-                        // Save the webpage image.
-                        new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
-                        break;
+                // Store the URL.
+                if (originalUrlString.startsWith("data:")) {
+                    // Save the original URL.
+                    saveWebpageUrl = originalUrlString;
+                } else {
+                    // Get the URL from the edit text, which may have been modified.
+                    saveWebpageUrl = dialogUrlEditText.getText().toString();
                 }
 
-                // Reset the strings.
-                saveWebpageUrl = "";
-                saveWebpageFilePath = "";
-            } else {  // The file path is in a public directory.
-                // Check if the user has previously denied the storage permission.
-                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
-                    // Instantiate the storage permission alert dialog.
-                    DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(saveType);
-
-                    // Show the storage permission alert dialog.  The permission will be requested when the dialog is closed.
-                    storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
-                } else {  // Show the permission request directly.
-                    // Request the write external storage permission according to the save type.  The URL will be saved when it finishes.
-                    ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, saveType);
-                }
-            }
-        }
-    }
+                // Save the URL.
+                new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(saveWebpageUrl);
+                break;
 
-    @Override
-    public void onCloseStoragePermissionDialog(int requestType) {
-        // Request the write external storage permission according to the request type.  The file will be opened when it finishes.
-        ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestType);
+            case SaveWebpageDialog.SAVE_ARCHIVE:
+                try {
+                    // Create a temporary MHT file.
+                    File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
 
-    }
+                    // Save the temporary MHT file.
+                    currentWebView.saveWebArchive(temporaryMhtFile.toString(), false, callbackValue -> {
+                        if (callbackValue != null) {  // The temporary MHT file was saved successfully.
+                            try {
+                                // Create a temporary MHT file input stream.
+                                FileInputStream temporaryMhtFileInputStream = new FileInputStream(temporaryMhtFile);
 
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included).
-        if (grantResults.length > 0) {
-            switch (requestCode) {
-                case StoragePermissionDialog.OPEN:
-                    // Check to see if the storage permission was granted.  If the dialog was canceled the grant results will be empty.
-                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  // The storage permission was granted.
-                        // Load the file.
-                        currentWebView.loadUrl("file://" + openFilePath);
-                    } else {  // The storage permission was not granted.
-                        // Display an error snackbar.
-                        Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
-                    }
-                    break;
-
-                case StoragePermissionDialog.SAVE_URL:
-                    // Check to see if the storage permission was granted.  If the dialog was canceled the grant results will be empty.
-                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  // The storage permission was granted.
-                        // Save the raw URL.
-                        new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
-                    } else {  // The storage permission was not granted.
-                        // Display an error snackbar.
-                        Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
-                    }
-                    break;
-
-                case StoragePermissionDialog.SAVE_ARCHIVE:
-                    // Check to see if the storage permission was granted.  If the dialog was canceled the grant results will be empty.
-                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  // The storage permission was granted.
-                        // Save the webpage archive.
-                        saveWebpageArchive(saveWebpageFilePath);
-                    } else {  // The storage permission was not granted.
-                        // Display an error snackbar.
-                        Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
-                    }
-                    break;
-
-                case StoragePermissionDialog.SAVE_IMAGE:
-                    // Check to see if the storage permission was granted.  If the dialog was canceled the grant results will be empty.
-                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  // The storage permission was granted.
-                        // Save the webpage image.
-                        new SaveWebpageImage(this, this, saveWebpageFilePath, currentWebView).execute();
-                    } else {  // The storage permission was not granted.
-                        // Display an error snackbar.
-                        Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
-                    }
-                    break;
-            }
+                                // Get an output stream for the save webpage file path.
+                                OutputStream mhtOutputStream = getContentResolver().openOutputStream(Uri.parse(saveWebpageFilePath));
+
+                                // Create a transfer byte array.
+                                byte[] transferByteArray = new byte[1024];
+
+                                // Create an integer to track the number of bytes read.
+                                int bytesRead;
 
-            // Reset the strings.
-            openFilePath = "";
-            saveWebpageUrl = "";
-            saveWebpageFilePath = "";
+                                // Copy the temporary MHT file input stream to the MHT output stream.
+                                while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) {
+                                    mhtOutputStream.write(transferByteArray, 0, bytesRead);
+                                }
+
+                                // Close the streams.
+                                mhtOutputStream.close();
+                                temporaryMhtFileInputStream.close();
+
+                                // Display a snackbar.
+                                Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + currentWebView.getCurrentUrl(), Snackbar.LENGTH_SHORT).show();
+                            } catch (Exception exception) {
+                                // Display a snackbar with the exception.
+                                Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+                            } finally {
+                                // Delete the temporary MHT file.
+                                //noinspection ResultOfMethodCallIgnored
+                                temporaryMhtFile.delete();
+                            }
+                        } else {  // There was an unspecified error while saving the temporary MHT file.
+                            // Display an error snackbar.
+                            Snackbar.make(currentWebView, getString(R.string.error_saving_file), Snackbar.LENGTH_INDEFINITE).show();
+                        }
+                    });
+                } catch (IOException ioException) {
+                    // Display a snackbar with the IO exception.
+                    Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show();
+                }
+                break;
+
+            case SaveWebpageDialog.SAVE_IMAGE:
+                // Save the webpage image.
+                new SaveWebpageImage(this, saveWebpageFilePath, currentWebView).execute();
+                break;
         }
     }
-
+    
+    // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
+    @SuppressLint("ClickableViewAccessibility")
     private void initializeApp() {
         // Get a handle for the input method.
         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -3159,9 +3183,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
         }
 
-        // Get handles for the URL views.
-        EditText urlEditText = findViewById(R.id.url_edittext);
-
         // Remove the formatting from the URL edit text when the user is editing the text.
         urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
             if (hasFocus) {  // The user is editing the URL text box.
@@ -3205,12 +3226,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Reset the waiting for proxy status.
                     waitingForProxy = false;
 
-                    // Get a handle for the waiting for proxy dialog.
-                    DialogFragment waitingForProxyDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog));
+                    // Get a list of the current fragments.
+                    List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
+
+                    // Check each fragment to see if it is a waiting for proxy dialog.  Sometimes more than one is displayed.
+                    for (int i = 0; i < fragmentList.size(); i++) {
+                        // Get the fragment tag.
+                        String fragmentTag = fragmentList.get(i).getTag();
 
-                    // Dismiss the waiting for proxy dialog if it is displayed.
-                    if (waitingForProxyDialogFragment != null) {
-                        waitingForProxyDialogFragment.dismiss();
+                        // Check to see if it is the waiting for proxy dialog.
+                        if ((fragmentTag!= null) && fragmentTag.equals(getString(R.string.waiting_for_proxy_dialog))) {
+                            // Dismiss the waiting for proxy dialog.
+                            ((DialogFragment) fragmentList.get(i)).dismiss();
+                        }
                     }
 
                     // Reload existing URLs and load any URLs that are waiting for the proxy.
@@ -3250,24 +3278,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
 
         // Get handles for views that need to be modified.
-        NavigationView navigationView = findViewById(R.id.navigationview);
-        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+        LinearLayout bookmarksHeaderLinearLayout = findViewById(R.id.bookmarks_header_linearlayout);
         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
 
-        // Listen for touches on the navigation menu.
-        navigationView.setNavigationItemSelectedListener(this);
-
-        // Get handles for the navigation menu and the back and forward menu items.
-        Menu navigationMenu = navigationView.getMenu();
-        MenuItem navigationBackMenuItem = navigationMenu.findItem(R.id.back);
-        MenuItem navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
-        MenuItem navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
-        MenuItem navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
-
         // Update the web view pager every time a tab is modified.
         webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
             @Override
@@ -3328,6 +3345,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
+        // Set a touch listener on the bookmarks header linear layout so that touches don't pass through to the button underneath.
+        bookmarksHeaderLinearLayout.setOnTouchListener((view, motionEvent) -> {
+            // Consume the touch.
+            return true;
+        });
+
         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
         launchBookmarksActivityFab.setOnClickListener(v -> {
             // Get a copy of the favorite icon bitmap.
@@ -3409,7 +3432,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         });
 
         // Implement swipe to refresh.
-        swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
+        swipeRefreshLayout.setOnRefreshListener(() -> {
+            // Check the visibility of the bottom app bar.  Sometimes it is hidden if the WebView is the same size as the visible screen.
+            if (bottomAppBar && scrollAppBar && (appBarLayout.getVisibility() == View.GONE)) {  // The bottom app bar is currently hidden.
+                // Show the app bar.
+                appBarLayout.setVisibility(View.VISIBLE);
+
+                // Disable the refreshing animation.
+                swipeRefreshLayout.setRefreshing(false);
+            } else {  // A bottom app bar is not currently hidden.
+                // Reload the website.
+                currentWebView.reload();
+            }
+        });
 
         // Store the default progress view offsets for use later in `initializeWebView()`.
         defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
@@ -3483,7 +3518,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Find out if the selected bookmark is a folder.
             boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
 
-            if (isFolder) {
+            // Check to see if the bookmark is a folder.
+            if (isFolder) {  // The bookmark is a folder.
                 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
                 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
 
@@ -3492,7 +3528,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Show the edit folder bookmark dialog.
                 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
-            } else {
+            } else {  // The bookmark is not a folder.
                 // Get the bookmark cursor for this ID.
                 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
 
@@ -3501,6 +3537,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Load the bookmark in a new tab but do not switch to the tab or close the drawer.
                 addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
+
+                // Display a snackbar.
+                Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
             }
 
             // Consume the event.
@@ -3519,6 +3558,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             @Override
             public void onDrawerClosed(@NonNull View drawerView) {
+                // Reset the drawer icon when the drawer is closed.  Otherwise, it is an arrow if the drawer is open when the app is restarted.
+                actionBarDrawerToggle.syncState();
             }
 
             @Override
@@ -3564,12 +3605,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Store the values from the shared preferences in variables.
         incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
-        boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
         sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
         sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
         sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
         proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
+        downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false);
         hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
         scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
 
@@ -3592,71 +3633,51 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             searchURL = searchString;
         }
 
-        // Get a handle for the app compat delegate.
-        AppCompatDelegate appCompatDelegate = getDelegate();
-
-        // Get handles for the views that need to be modified.
-        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-        ActionBar actionBar = appCompatDelegate.getSupportActionBar();
-
-        // Remove the incorrect lint warning below that the action bar might be null.
-        assert actionBar != null;
-
         // Apply the proxy.
         applyProxy(false);
 
-        // Set Do Not Track status.
-        if (doNotTrackEnabled) {
-            customHeaders.put("DNT", "1");
-        } else {
-            customHeaders.remove("DNT");
-        }
-
-        // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
-        CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-        AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
-        AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
-        AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
-
-        // Add the scrolling behavior to the layout parameters.
-        if (scrollAppBar) {
-            // Enable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
-            toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-        } else {
-            // Disable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(null);
-            toolbarLayoutParams.setScrollFlags(0);
-            findOnPageLayoutParams.setScrollFlags(0);
-            tabsLayoutParams.setScrollFlags(0);
-
-            // Expand the app bar if it is currently collapsed.
-            appBarLayout.setExpanded(true);
-        }
-
-        // Apply the modified layout parameters.
-        swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
-        toolbar.setLayoutParams(toolbarLayoutParams);
-        findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
-        tabsLinearLayout.setLayoutParams(tabsLayoutParams);
+        // Adjust the layout and scrolling parameters if the app bar is at the top of the screen.
+        if (!bottomAppBar) {
+            // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
+            CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+            AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
+            AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
+            AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
+
+            // Add the scrolling behavior to the layout parameters.
+            if (scrollAppBar) {
+                // Enable scrolling of the app bar.
+                swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+                toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+                findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+                tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+            } else {
+                // Disable scrolling of the app bar.
+                swipeRefreshLayoutParams.setBehavior(null);
+                toolbarLayoutParams.setScrollFlags(0);
+                findOnPageLayoutParams.setScrollFlags(0);
+                tabsLayoutParams.setScrollFlags(0);
+
+                // Expand the app bar if it is currently collapsed.
+                appBarLayout.setExpanded(true);
+            }
 
-        // Set the app bar scrolling for each WebView.
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+            // Set the app bar scrolling for each WebView.
+            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
 
-            // Only modify the WebViews if they exist.
-            if (fragmentView != null) {
-                // Get the nested scroll WebView from the tab fragment.
-                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+                // Only modify the WebViews if they exist.
+                if (fragmentView != null) {
+                    // Get the nested scroll WebView from the tab fragment.
+                    NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                // Set the app bar scrolling.
-                nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
+                    // Set the app bar scrolling.
+                    nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
+                }
             }
         }
 
@@ -3679,7 +3700,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Hide the banner ad in the free flavor.
             if (BuildConfig.FLAVOR.contentEquals("free")) {
-                AdHelper.hideAd(findViewById(R.id.adview));
+                // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+                View adView = findViewById(R.id.adview);
+
+                // Hide the banner ad.
+                AdHelper.hideAd(adView);
             }
 
             /* Hide the system bars.
@@ -3702,8 +3727,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Show the banner ad in the free flavor.
             if (BuildConfig.FLAVOR.contentEquals("free")) {
+                // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+                View adView = findViewById(R.id.adview);
+
                 // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
-                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
+                // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
+                AdHelper.initializeAds(adView, getApplicationContext(), this, getSupportFragmentManager(), getString(R.string.ad_unit_id));
             }
 
             // Remove the `SYSTEM_UI` flags from the root frame layout.
@@ -3712,7 +3741,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
-    public void navigateHistory(String url, int steps) {
+    public void navigateHistory(@NonNull String url, int steps) {
         // Apply the domain settings.
         applyDomainSettings(currentWebView, url, false, false, false);
 
@@ -3866,10 +3895,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Get a handle for the cookie manager.
             CookieManager cookieManager = CookieManager.getInstance();
 
-            // Get handles for the views.
-            RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
-            SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-
             // Initialize the user agent array adapter and string array.
             ArrayAdapter<CharSequence> userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
             String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
@@ -3882,8 +3907,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the settings from the cursor.
                 nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
                 nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
-                nestedScrollWebView.setAcceptFirstPartyCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
-                boolean domainThirdPartyCookiesEnabled = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
+                nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.COOKIES)) == 1);
                 nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
                 // Form data can be removed once the minimum API >= 26.
                 boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
@@ -3953,12 +3977,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Apply the cookie domain settings.
-                cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
-
-                // Set third-party cookies status if API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, domainThirdPartyCookiesEnabled);
-                }
+                cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
 
                 // Apply the form data setting if the API < 26.
                 if (Build.VERSION.SDK_INT < 26) {
@@ -4033,7 +4052,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Update the swipe refresh layout.
                         if (defaultSwipeToRefresh) {  // 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.
-                            swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+                            swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
                         } else {  // Swipe to refresh is disabled.
                             // Disable the swipe refresh layout.
                             swipeRefreshLayout.setEnabled(false);
@@ -4045,7 +4064,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         nestedScrollWebView.setSwipeToRefresh(true);
 
                         // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
-                        swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+                        swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
                         break;
 
                     case DomainsDatabaseHelper.DISABLED:
@@ -4137,8 +4156,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             } else {  // The new URL does not have custom domain settings.  Load the defaults.
                 // Store the values from the shared preferences.
                 nestedScrollWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
-                nestedScrollWebView.setAcceptFirstPartyCookies(sharedPreferences.getBoolean("first_party_cookies", false));
-                boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
+                nestedScrollWebView.setAcceptCookies(sharedPreferences.getBoolean(getString(R.string.cookies_key), false));
                 nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
                 boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true));
@@ -4149,8 +4167,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
 
-                // Apply the default first-party cookie setting.
-                cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
+                // Apply the default cookie setting.
+                cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
 
                 // Apply the default font size setting.
                 try {
@@ -4172,7 +4190,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Update the swipe refresh layout.
                 if (defaultSwipeToRefresh) {  // 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.
-                    swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+                    swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
                 } else {  // Swipe to refresh is disabled.
                     // Disable the swipe refresh layout.
                     swipeRefreshLayout.setEnabled(false);
@@ -4181,11 +4199,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Reset the pinned variables.
                 nestedScrollWebView.setDomainSettingsDatabaseId(-1);
 
-                // Set third-party cookies status if API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled);
-                }
-
                 // Get the array position of the user agent name.
                 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
 
@@ -4241,7 +4254,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Set the loading of webpage images.
                 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
 
-                // Set a transparent background on URL edit text.
+                // Set a transparent background on the URL relative layout.
                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
             }
 
@@ -4315,8 +4328,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Get a handle for the waiting for proxy alert dialog.
                             DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog();
 
-                            // Display the waiting for proxy alert dialog.
-                            waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
+                            // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                            try {
+                                // Show the waiting for proxy alert dialog.
+                                waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
+                            } catch (Exception waitingForTorException) {
+                                // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                                pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
+                            }
                         }
                     }
                 } catch (PackageManager.NameNotFoundException exception) {  // Orbot is not installed.
@@ -4325,8 +4344,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Get a handle for the Orbot not installed alert dialog.
                         DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
 
-                        // Display the Orbot not installed alert dialog.
-                        orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                        try {
+                            // Display the Orbot not installed alert dialog.
+                            orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        } catch (Exception orbotNotInstalledException) {
+                            // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                            pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                        }
                     }
                 }
                 break;
@@ -4345,15 +4370,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     PackageManager packageManager = getPackageManager();
 
                     // Check to see if I2P is in the list.  This will throw an error and drop to the catch section if it isn't installed.
-                    packageManager.getPackageInfo("org.torproject.android", 0);
+                    packageManager.getPackageInfo("net.i2p.android.router", 0);
                 } catch (PackageManager.NameNotFoundException exception) {  // I2P is not installed.
                     // Sow the I2P not installed dialog if it is not already displayed.
                     if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
                         // Get a handle for the waiting for proxy alert dialog.
                         DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
 
-                        // Display the I2P not installed alert dialog.
-                        i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                        try {
+                            // Display the I2P not installed alert dialog.
+                            i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        } catch (Exception i2pNotInstalledException) {
+                            // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                            pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                        }
                     }
                 }
                 break;
@@ -4393,48 +4424,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
         // Only update the privacy icons if the options menu and the current WebView have already been populated.
         if ((optionsMenu != null) && (currentWebView != null)) {
-            // Get handles for the menu items.
-            MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript);
-            MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies);
-            MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
-
             // Update the privacy icon.
             if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is enabled.
-                privacyMenuItem.setIcon(R.drawable.javascript_enabled);
-            } else if (currentWebView.getAcceptFirstPartyCookies()) {  // JavaScript is disabled but cookies are enabled.
-                privacyMenuItem.setIcon(R.drawable.warning);
+                optionsPrivacyMenuItem.setIcon(R.drawable.javascript_enabled);
+            } else if (currentWebView.getAcceptCookies()) {  // JavaScript is disabled but cookies are enabled.
+                optionsPrivacyMenuItem.setIcon(R.drawable.warning);
             } else {  // All the dangerous features are disabled.
-                privacyMenuItem.setIcon(R.drawable.privacy_mode);
+                optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode);
             }
 
             // Get the current theme status.
             int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
-            // Update the first-party cookies icon.
-            if (currentWebView.getAcceptFirstPartyCookies()) {  // First-party cookies are enabled.
-                firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
-            } else {  // First-party cookies are disabled.
+            // Update the cookies icon.
+            if (currentWebView.getAcceptCookies()) {  // Cookies are enabled.
+                optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
+            } else {  // Cookies are disabled.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
+                    optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
                 } else {
-                    firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
+                    optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
                 }
             }
 
             // Update the refresh icon.
-            if (refreshMenuItem.getTitle() == getString(R.string.refresh)) {  // The refresh icon is displayed.
+            if (optionsRefreshMenuItem.getTitle() == getString(R.string.refresh)) {  // The refresh icon is displayed.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    refreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
+                    optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
                 } else {
-                    refreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
+                    optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
                 }
             } else {  // The stop icon is displayed.
                 // Set the icon according to the theme.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    refreshMenuItem.setIcon(R.drawable.close_blue_day);
+                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
                 } else {
-                    refreshMenuItem.setIcon(R.drawable.close_blue_night);
+                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
                 }
             }
 
@@ -4446,9 +4472,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void highlightUrlText() {
-        // Get a handle for the URL edit text.
-        EditText urlEditText = findViewById(R.id.url_edittext);
-
         // Only highlight the URL text if the box is not currently selected.
         if (!urlEditText.hasFocus()) {
             // Get the URL string.
@@ -4510,11 +4533,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-        // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
+        // Populate the bookmarks cursor adapter.
         bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
             @Override
             public View newView(Context context, Cursor cursor, ViewGroup parent) {
-                // Inflate the individual item layout.  `false` does not attach it to the root.
+                // Inflate the individual item layout.
                 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
             }
 
@@ -4762,6 +4785,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void addNewTab(String url, boolean moveToTab) {
+        // Clear the focus from the URL edit text, so that it will be populated with the information from the new tab.
+        urlEditText.clearFocus();
+
         // Get the new page number.  The page numbers are 0 indexed, so the new page number will match the current count.
         int newTabNumber = tabLayout.getTabCount();
 
@@ -4779,6 +4805,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Add the new WebView page.
         webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab);
+
+        // Show the app bar if it is at the bottom of the screen and the new tab is taking focus.
+        if (bottomAppBar && moveToTab) {
+            appBarLayout.setVisibility(View.VISIBLE);
+        }
     }
 
     public void closeTab(View view) {
@@ -4792,64 +4823,79 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void closeCurrentTab() {
-        // Pause the current WebView.  This prevents buffered audio from playing after the tab is closed.
-        currentWebView.onPause();
-
         // Get the current tab number.
         int currentTabNumber = tabLayout.getSelectedTabPosition();
 
         // Delete the current tab.
         tabLayout.removeTabAt(currentTabNumber);
 
-        // Delete the current page.  If the selected page number did not change during the delete, it will return true, meaning that the current WebView must be reset.
+        // 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,
+        // meaning that the current WebView must be reset.  Otherwise it will happen automatically as the selected tab number changes.
         if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
             setCurrentWebView(currentTabNumber);
         }
-
-        // Expand the app bar if it is currently collapsed.
-        appBarLayout.setExpanded(true);
     }
 
-    private void saveWebpageArchive(String filePath) {
-        // Save the webpage archive.
-        currentWebView.saveWebArchive(filePath);
+    private void exitFullScreenVideo() {
+        // Re-enable the screen timeout.
+        fullScreenVideoFrameLayout.setKeepScreenOn(false);
 
-        // Display a snackbar.
-        Snackbar saveWebpageArchiveSnackbar = Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + filePath, Snackbar.LENGTH_SHORT);
+        // Unset the full screen video flag.
+        displayingFullScreenVideo = false;
 
-        // Add an open option to the snackbar.
-        saveWebpageArchiveSnackbar.setAction(R.string.open, (View view) -> {
-            // Get a file for the file name string.
-            File file = new File(filePath);
+        // Remove all the views from the full screen video frame layout.
+        fullScreenVideoFrameLayout.removeAllViews();
 
-            // Declare a file URI variable.
-            Uri fileUri;
+        // Hide the full screen video frame layout.
+        fullScreenVideoFrameLayout.setVisibility(View.GONE);
 
-            // Get the URI for the file according to the Android version.
-            if (Build.VERSION.SDK_INT >= 24) {  // Use a file provider.
-                fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file);
-            } else {  // Get the raw file path URI.
-                fileUri = Uri.fromFile(file);
-            }
+        // Enable the sliding drawers.
+        drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
+
+        // Show the main content relative layout.
+        mainContentRelativeLayout.setVisibility(View.VISIBLE);
 
-            // Get a handle for the content resolver.
-            ContentResolver contentResolver = getContentResolver();
+        // Apply the appropriate full screen mode flags.
+        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+            // Hide the app bar if specified.
+            if (hideAppBar) {
+                // Hide the tab linear layout.
+                tabsLinearLayout.setVisibility(View.GONE);
 
-            // Create an open intent with `ACTION_VIEW`.
-            Intent openIntent = new Intent(Intent.ACTION_VIEW);
+                // Hide the action bar.
+                actionBar.hide();
+            }
 
-            // Set the URI and the MIME type.
-            openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri));
+            // Hide the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+                View adView = findViewById(R.id.adview);
 
-            // Allow the app to read the file URI.
-            openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                // Hide the banner ad.
+                AdHelper.hideAd(adView);
+            }
 
-            // Show the chooser.
-            startActivity(Intent.createChooser(openIntent, getString(R.string.open)));
-        });
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else {  // Switch to normal viewing mode.
+            // Remove the `SYSTEM_UI` flags from the root frame layout.
+            rootFrameLayout.setSystemUiVisibility(0);
+        }
+
+        // Reload the ad for the free flavor if not in full screen mode.
+        if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+            // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+            View adView = findViewById(R.id.adview);
 
-        // Show the snackbar.
-        saveWebpageArchiveSnackbar.show();
+            // Reload the ad.
+            AdHelper.loadAd(adView, this, this, getString(R.string.ad_unit_id));
+        }
     }
 
     private void clearAndExit() {
@@ -4960,13 +5006,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the WebView tab fragment.
                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-                // Get the fragment view.
-                View fragmentView = webViewTabFragment.getView();
+                // Get the WebView fragment view.
+                View webViewFragmentView = webViewTabFragment.getView();
 
                 // Only clear the cache if the WebView exists.
-                if (fragmentView != null) {
+                if (webViewFragmentView != null) {
                     // Get the nested scroll WebView from the tab fragment.
-                    NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+                    NestedScrollWebView nestedScrollWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview);
 
                     // Clear the cache for this WebView.
                     nestedScrollWebView.clearCache(true);
@@ -4995,13 +5041,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Get the WebView tab fragment.
             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+            // Get the WebView frame layout.
+            FrameLayout webViewFrameLayout = (FrameLayout) webViewTabFragment.getView();
 
             // Only wipe out the WebView if it exists.
-            if (fragmentView != null) {
+            if (webViewFrameLayout != null) {
                 // Get the nested scroll WebView from the tab fragment.
-                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+                NestedScrollWebView nestedScrollWebView = webViewFrameLayout.findViewById(R.id.nestedscroll_webview);
 
                 // Clear SSL certificate preferences for this WebView.
                 nestedScrollWebView.clearSslPreferences();
@@ -5009,6 +5055,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Clear the back/forward history for this WebView.
                 nestedScrollWebView.clearHistory();
 
+                // Remove all the views from the frame layout.
+                webViewFrameLayout.removeAllViews();
+
                 // Destroy the internal state of the WebView.
                 nestedScrollWebView.destroy();
             }
@@ -5056,11 +5105,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private void setCurrentWebView(int pageNumber) {
-        // Get handles for the URL views.
-        RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
-        EditText urlEditText = findViewById(R.id.url_edittext);
-        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-
         // Stop the swipe to refresh indicator if it is running
         swipeRefreshLayout.setRefreshing(false);
 
@@ -5068,17 +5112,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(pageNumber);
 
         // Get the fragment view.
-        View fragmentView = webViewTabFragment.getView();
+        View webViewFragmentView = webViewTabFragment.getView();
 
         // Set the current WebView if the fragment view is not null.
-        if (fragmentView != null) {  // The fragment has been populated.
+        if (webViewFragmentView != null) {  // The fragment has been populated.
             // Store the current WebView.
-            currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+            currentWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview);
 
             // Update the status of swipe to refresh.
             if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
                 // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.  It is updated every time the scroll changes.
-                swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+                swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
             } else {  // Swipe to refresh is disabled.
                 // Disable the swipe refresh layout.
                 swipeRefreshLayout.setEnabled(false);
@@ -5087,8 +5131,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Get a handle for the cookie manager.
             CookieManager cookieManager = CookieManager.getInstance();
 
-            // Set the first-party cookie status.
-            cookieManager.setAcceptCookie(currentWebView.getAcceptFirstPartyCookies());
+            // Set the cookie status.
+            cookieManager.setAcceptCookie(currentWebView.getAcceptCookies());
 
             // Update the privacy icons.  `true` redraws the icons in the app bar.
             updatePrivacyIcons(true);
@@ -5203,20 +5247,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         }
 
-        // Get a handle for the app compat delegate.
-        AppCompatDelegate appCompatDelegate = getDelegate();
-
-        // Get handles for the activity views.
-        FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
-        RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
-        ActionBar actionBar = appCompatDelegate.getSupportActionBar();
-        LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
-        EditText urlEditText = findViewById(R.id.url_edittext);
-        SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
-
-        // Remove the incorrect lint warning below that the action bar might be null.
-        assert actionBar != null;
-
         // Get a handle for the activity
         Activity activity = this;
 
@@ -5252,6 +5282,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Explicitly disable geolocation.
         nestedScrollWebView.getSettings().setGeolocationEnabled(false);
 
+        // Allow loading of file:// URLs.  This is necessary for opening MHT web archives, which are copies into a temporary cache location.
+        nestedScrollWebView.getSettings().setAllowFileAccess(true);
+
         // Create a double-tap gesture detector to toggle full-screen mode.
         GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
             // Override `onDoubleTap()`.  All other events are handled using the default settings.
@@ -5274,25 +5307,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Hide the action bar.
                             actionBar.hide();
 
-                            // Check to see if the app bar is normally scrolled.
-                            if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
-                                // Get the swipe refresh layout parameters.
-                                CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-
-                                // Remove the off-screen scrolling layout.
-                                swipeRefreshLayoutParams.setBehavior(null);
-                            } else {  // The app bar is not scrolled when it is displayed.
-                                // Remove the padding from the top of the swipe refresh layout.
-                                swipeRefreshLayout.setPadding(0, 0, 0, 0);
-
-                                // The swipe refresh circle must be moved above the now removed status bar location.
-                                swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
+                            // Set layout and scrolling parameters if the app bar is at the top of the screen.
+                            if (!bottomAppBar) {
+                                // Check to see if the app bar is normally scrolled.
+                                if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
+                                    // Get the swipe refresh layout parameters.
+                                    CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+
+                                    // Remove the off-screen scrolling layout.
+                                    swipeRefreshLayoutParams.setBehavior(null);
+                                } else {  // The app bar is not scrolled when it is displayed.
+                                    // Remove the padding from the top of the swipe refresh layout.
+                                    swipeRefreshLayout.setPadding(0, 0, 0, 0);
+
+                                    // The swipe refresh circle must be moved above the now removed status bar location.
+                                    swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
+                                }
                             }
                         }
 
                         // Hide the banner ad in the free flavor.
                         if (BuildConfig.FLAVOR.contentEquals("free")) {
-                            AdHelper.hideAd(findViewById(R.id.adview));
+                            // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+                            View adView = findViewById(R.id.adview);
+
+                            // Hide the banner ad.
+                            AdHelper.hideAd(adView);
                         }
 
                         /* Hide the system bars.
@@ -5312,26 +5352,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Show the action bar.
                             actionBar.show();
 
-                            // Check to see if the app bar is normally scrolled.
-                            if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
-                                // Get the swipe refresh layout parameters.
-                                CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-
-                                // Add the off-screen scrolling layout.
-                                swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
-                            } else {  // The app bar is not scrolled when it is displayed.
-                                // The swipe refresh layout must be manually moved below the app bar layout.
-                                swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
-
-                                // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                                swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                            // Set layout and scrolling parameters if the app bar is at the top of the screen.
+                            if (!bottomAppBar) {
+                                // Check to see if the app bar is normally scrolled.
+                                if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
+                                    // Get the swipe refresh layout parameters.
+                                    CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+
+                                    // Add the off-screen scrolling layout.
+                                    swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+                                } else {  // The app bar is not scrolled when it is displayed.
+                                    // The swipe refresh layout must be manually moved below the app bar layout.
+                                    swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
+
+                                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                                }
                             }
                         }
 
                         // Show the banner ad in the free flavor.
                         if (BuildConfig.FLAVOR.contentEquals("free")) {
-                            // Reload the ad.
-                            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
+                            // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+                            View adView = findViewById(R.id.adview);
+
+                            // Reload the ad.  `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
+                            AdHelper.loadAd(adView, getApplicationContext(), activity, getString(R.string.ad_unit_id));
                         }
 
                         // Remove the `SYSTEM_UI` flags from the root frame layout.
@@ -5360,27 +5406,38 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Allow the downloading of files.
         nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
-            // Define a formatted file size string.
-            String formattedFileSizeString;
-
-            // Process the content length if it contains data.
-            if (contentLength > 0) {  // The content length is greater than 0.
-                // Format the content length as a string.
-                formattedFileSizeString = NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes);
-            } else {  // The content length is not greater than 0.
-                // Set the formatted file size string to be `unknown size`.
-                formattedFileSizeString = getString(R.string.unknown_size);
-            }
+            // Check the download preference.
+            if (downloadWithExternalApp) {  // Download with an external app.
+                downloadUrlWithExternalApp(downloadUrl);
+            } else {  // Handle the download inside of Privacy Browser.
+                // Define a formatted file size string.
+                String formattedFileSizeString;
+
+                // Process the content length if it contains data.
+                if (contentLength > 0) {  // The content length is greater than 0.
+                    // Format the content length as a string.
+                    formattedFileSizeString = NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes);
+                } else {  // The content length is not greater than 0.
+                    // Set the formatted file size string to be `unknown size`.
+                    formattedFileSizeString = getString(R.string.unknown_size);
+                }
 
-            // Get the file name from the content disposition.
-            String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
+                // Get the file name from the content disposition.
+                String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
 
-            // Instantiate the save dialog.
-            DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
-                    nestedScrollWebView.getAcceptFirstPartyCookies());
+                // Instantiate the save dialog.
+                DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+                        nestedScrollWebView.getAcceptCookies());
 
-            // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
-            saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                // 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.
+                try {
+                    // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
+                    saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                } catch (Exception exception) {  // The dialog could not be shown.
+                    // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                    pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog)));
+                }
+            }
         });
 
         // Update the find on page count.
@@ -5409,7 +5466,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.  Also reinforce full screen browsing mode.
         // On API < 23, `getViewTreeObserver().addOnScrollChangedListener()` must be used, but it is a little bit buggy and appears to get garbage collected from time to time.
         if (Build.VERSION.SDK_INT >= 23) {
-            nestedScrollWebView.setOnScrollChangeListener((view, i, i1, i2, i3) -> {
+            nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                // Set the swipe to refresh status.
                 if (nestedScrollWebView.getSwipeToRefresh()) {
                     // Only enable swipe to refresh if the WebView is scrolled to the top.
                     swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
@@ -5418,6 +5476,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     swipeRefreshLayout.setEnabled(false);
                 }
 
+                //  Set the visibility of the bottom app bar.
+                if (bottomAppBar && scrollAppBar && (Calendar.getInstance().getTimeInMillis() - lastScrollUpdate > 100)) {
+                    if (scrollY - oldScrollY > 25) {  // The WebView was scrolled down.
+                        appBarLayout.setVisibility(View.GONE);
+                    } else if (scrollY - oldScrollY < -25) {  // The WebView was scrolled up.
+                        appBarLayout.setVisibility(View.VISIBLE);
+                    }
+
+                    // Update the last scroll update variable.  This prevents the app bar from flashing on and off at the bottom of the screen.
+                    lastScrollUpdate = Calendar.getInstance().getTimeInMillis();
+                }
+
                 // Reinforce the system UI visibility flags if in full screen browsing mode.
                 // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
                 if (inFullScreenBrowsingMode) {
@@ -5441,7 +5511,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     swipeRefreshLayout.setEnabled(false);
                 }
 
-
                 // Reinforce the system UI visibility flags if in full screen browsing mode.
                 // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
                 if (inFullScreenBrowsingMode) {
@@ -5546,16 +5615,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Enter full screen video.
             @Override
             public void onShowCustomView(View video, CustomViewCallback callback) {
-                // Get a handle for the full screen video frame layout.
-                FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
-
                 // Set the full screen video flag.
                 displayingFullScreenVideo = true;
 
                 // Pause the ad if this is the free flavor.
                 if (BuildConfig.FLAVOR.contentEquals("free")) {
+                    // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+                    View adView = findViewById(R.id.adview);
+
                     // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
-                    AdHelper.pauseAd(findViewById(R.id.adview));
+                    AdHelper.pauseAd(adView);
                 }
 
                 // Hide the keyboard.
@@ -5589,61 +5658,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Exit full screen video.
             @Override
             public void onHideCustomView() {
-                // Get a handle for the full screen video frame layout.
-                FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
-
-                // Re-enable the screen timeout.
-                fullScreenVideoFrameLayout.setKeepScreenOn(false);
-
-                // Unset the full screen video flag.
-                displayingFullScreenVideo = false;
-
-                // Remove all the views from the full screen video frame layout.
-                fullScreenVideoFrameLayout.removeAllViews();
-
-                // Hide the full screen video frame layout.
-                fullScreenVideoFrameLayout.setVisibility(View.GONE);
-
-                // Enable the sliding drawers.
-                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
-                // Show the main content relative layout.
-                mainContentRelativeLayout.setVisibility(View.VISIBLE);
-
-                // Apply the appropriate full screen mode flags.
-                if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-                    // Hide the app bar if specified.
-                    if (hideAppBar) {
-                        // Hide the tab linear layout.
-                        tabsLinearLayout.setVisibility(View.GONE);
-
-                        // Hide the action bar.
-                        actionBar.hide();
-                    }
-
-                    // Hide the banner ad in the free flavor.
-                    if (BuildConfig.FLAVOR.contentEquals("free")) {
-                        AdHelper.hideAd(findViewById(R.id.adview));
-                    }
-
-                    /* Hide the system bars.
-                     * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                     * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                     * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                     * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                     */
-                    rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-                } else {  // Switch to normal viewing mode.
-                    // Remove the `SYSTEM_UI` flags from the root frame layout.
-                    rootFrameLayout.setSystemUiVisibility(0);
-                }
-
-                // Reload the ad for the free flavor if not in full screen mode.
-                if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-                    // Reload the ad.
-                    AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
-                }
+                // Exit the full screen video.
+                exitFullScreenVideo();
             }
 
             // Upload files.
@@ -5789,15 +5805,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Sanitize the URL.
                 url = sanitizeUrl(url);
 
-                // Get a handle for the navigation view.
-                NavigationView navigationView = findViewById(R.id.navigationview);
-
-                // Get a handle for the navigation menu.
-                Menu navigationMenu = navigationView.getMenu();
-
-                // Get a handle for the navigation requests menu item.
-                MenuItem navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
-
                 // Create an empty web resource response to be used if the resource request is blocked.
                 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
 
@@ -5864,8 +5871,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                             // Update the options menu if it has been populated.
                             if (optionsMenu != null) {
-                                optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                optionsMenu.findItem(R.id.block_all_third_party_requests).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
+                                optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                optionsBlockAllThirdPartyRequestsMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
                                         getString(R.string.block_all_third_party_requests));
                             }
                         });
@@ -5881,7 +5888,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
 
                     // Process the UltraList results.
-                    if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraLists's blacklist.
+                    if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraList's blacklist.
                         // Add the result to the resource requests.
                         nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
 
@@ -5898,8 +5905,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
-                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.ultralist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
+                                    optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsUltraListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
                                 }
                             });
                         }
@@ -5939,8 +5946,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
-                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
+                                    optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsUltraPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
                                 }
                             });
                         }
@@ -5980,8 +5987,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
-                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
+                                    optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsEasyListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
                                 }
                             });
                         }
@@ -6018,8 +6025,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
-                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
+                                    optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsEasyPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
                                 }
                             });
                         }
@@ -6056,8 +6063,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
-                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.fanboys_annoyance_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
+                                    optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsFanboysAnnoyanceListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
                                             getString(R.string.fanboys_annoyance_list));
                                 }
                             });
@@ -6093,8 +6100,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                                 // Update the options menu if it has been populated.
                                 if (optionsMenu != null) {
-                                    optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
-                                    optionsMenu.findItem(R.id.fanboys_social_blocking_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
+                                    optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+                                    optionsFanboysSocialBlockingListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
                                             getString(R.string.fanboys_social_blocking_list));
                                 }
                             });
@@ -6135,25 +6142,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             @Override
             public void onPageStarted(WebView view, String url, Bitmap favicon) {
-                // Get the preferences.
-                boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
-
-                // 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.
-                if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
-                    // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
-                    swipeRefreshLayout.setPadding(0, 0, 0, 0);
-
-                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
-                } else {
-                    // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
-                    appBarHeight = appBarLayout.getHeight();
-
-                    // The swipe refresh layout must be manually moved below the app bar layout.
-                    swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
-
-                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                // Set the padding and layout settings if the app bar is at the top of the screen.
+                if (!bottomAppBar) {
+                    // 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.
+                    if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
+                        // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
+                        swipeRefreshLayout.setPadding(0, 0, 0, 0);
+
+                        // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                        swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
+                    } else {
+                        // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
+                        appBarHeight = appBarLayout.getHeight();
+
+                        // The swipe refresh layout must be manually moved below the app bar layout.
+                        swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
+
+                        // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                        swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                    }
                 }
 
                 // Reset the list of resource requests.
@@ -6170,7 +6177,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Display the formatted URL text.
                     urlEditText.setText(url);
 
-                    // Apply text highlighting to `urlTextBox`.
+                    // Apply text highlighting to the URL text box.
                     highlightUrlText();
 
                     // Hide the keyboard.
@@ -6188,11 +6195,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Replace Refresh with Stop if the options menu has been created.  (The first WebView typically begins loading before the menu items are instantiated.)
                 if (optionsMenu != null) {
-                    // Get a handle for the refresh menu item.
-                    MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
-
                     // Set the title.
-                    refreshMenuItem.setTitle(R.string.stop);
+                    optionsRefreshMenuItem.setTitle(R.string.stop);
 
                     // Get the app bar and theme preferences.
                     boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
@@ -6204,9 +6208,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // Set the stop icon according to the theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                            refreshMenuItem.setIcon(R.drawable.close_blue_day);
+                            optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
                         } else {
-                            refreshMenuItem.setIcon(R.drawable.close_blue_night);
+                            optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
                         }
                     }
                 }
@@ -6215,17 +6219,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             @Override
             public void onPageFinished(WebView view, String url) {
                 // Flush any cookies to persistent storage.  The cookie manager has become very lazy about flushing cookies in recent versions.
-                if (nestedScrollWebView.getAcceptFirstPartyCookies() && Build.VERSION.SDK_INT >= 21) {
+                if (nestedScrollWebView.getAcceptCookies() && Build.VERSION.SDK_INT >= 21) {
                     CookieManager.getInstance().flush();
                 }
 
                 // Update the Refresh menu item if the options menu has been created.
                 if (optionsMenu != null) {
-                    // Get a handle for the refresh menu item.
-                    MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
-
                     // Reset the Refresh title.
-                    refreshMenuItem.setTitle(R.string.refresh);
+                    optionsRefreshMenuItem.setTitle(R.string.refresh);
 
                     // Get the app bar and theme preferences.
                     boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
@@ -6237,9 +6238,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // Set the icon according to the theme.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
+                            optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
                         } else {
-                            refreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
+                            optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
                         }
                     }
                 }
@@ -6390,8 +6391,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Instantiate an SSL certificate error alert dialog.
                     DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
 
-                    // Show the SSL certificate error dialog.
-                    sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+                    // 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.
+                    try {
+                        // Show the SSL certificate error dialog.
+                        sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+                    } catch (Exception exception) {
+                        // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                        pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
+                    }
                 }
             }
         });