]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Update Download with External App summary. https://redmine.stoutner.com/issues/921
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index d08f7c63c625a2c45566061891ad9167b954de7a..1ee31db40a03cb8dfe9996eacfeaa79140aa230e 100644 (file)
@@ -1,26 +1,27 @@
 /*
- * Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2015-2022 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
  *
- * Privacy Browser is free software: you can redistribute it and/or modify
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
- * Privacy Browser is distributed in the hope that it will be useful,
+ * Privacy Browser Android is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package com.stoutner.privacybrowser.activities;
 
+import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Dialog;
@@ -55,6 +56,7 @@ import android.preference.PreferenceManager;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintManager;
 import android.provider.DocumentsContract;
+import android.provider.OpenableColumns;
 import android.text.Editable;
 import android.text.Spanned;
 import android.text.TextWatcher;
@@ -77,6 +79,7 @@ import android.webkit.SslErrorHandler;
 import android.webkit.ValueCallback;
 import android.webkit.WebBackForwardList;
 import android.webkit.WebChromeClient;
+import android.webkit.WebResourceRequest;
 import android.webkit.WebResourceResponse;
 import android.webkit.WebSettings;
 import android.webkit.WebStorage;
@@ -96,6 +99,10 @@ import android.widget.RadioButton;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.activity.OnBackPressedCallback;
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.ActionBarDrawerToggle;
@@ -107,6 +114,7 @@ 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;
@@ -118,7 +126,6 @@ import com.google.android.material.navigation.NavigationView;
 import com.google.android.material.snackbar.Snackbar;
 import com.google.android.material.tabs.TabLayout;
 
-import com.stoutner.privacybrowser.BuildConfig;
 import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
@@ -126,7 +133,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.dialogs.AdConsentDialog;
+import com.stoutner.privacybrowser.dataclasses.PendingDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
@@ -136,17 +143,17 @@ import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
 import com.stoutner.privacybrowser.dialogs.OpenDialog;
 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
-import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
+import com.stoutner.privacybrowser.dialogs.SaveDialog;
 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
-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.ProxyHelper;
+import com.stoutner.privacybrowser.helpers.SanitizeUrlHelper;
 import com.stoutner.privacybrowser.views.NestedScrollWebView;
 
 import java.io.ByteArrayInputStream;
@@ -167,55 +174,46 @@ 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;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import kotlin.Pair;
+
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
         EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
-        PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, UrlHistoryDialog.NavigateHistoryListener,
+        PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener,
         WebViewTabFragment.NewTabListener {
 
-    // The executor service handles background tasks.  It is accessed from `ViewSourceActivity`.
-    public static ExecutorService executorService = Executors.newFixedThreadPool(4);
-
-    // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
+    // Define the public static variables.
+    public static final ExecutorService executorService = Executors.newFixedThreadPool(4);
     public static String orbotStatus = "unknown";
+    public static final ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
+    public static String proxyMode = ProxyHelper.NONE;
 
-    // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`.  It is also used in `onCreate()`, `onResume()`, and `addTab()`.
-    public static WebViewPagerAdapter webViewPagerAdapter;
-
-    // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
+    // Declare the public static variables.
+    public static String currentBookmarksFolder = "";
     public static boolean restartFromBookmarksActivity;
+    public static WebViewPagerAdapter webViewPagerAdapter;
 
-    // `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;
+    // Declare the public static views.
+    public static AppBarLayout appBarLayout;
 
     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
     public final static int UNRECOGNIZED_USER_AGENT = -1;
     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
-    public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
+    public final static int SETTINGS_CUSTOM_USER_AGENT = 11;
     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
-    public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
+    public final static int DOMAINS_CUSTOM_USER_AGENT = 12;
 
-    // Define the start activity for result request codes.  The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
+    // Define the start activity for result request codes.  The public static entry is accessed from `OpenDialog()`.
     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()`.
-    // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
-    public static String proxyMode = ProxyHelper.NONE;
 
     // Define the saved instance state constants.
     private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
@@ -237,9 +235,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
     private NestedScrollWebView currentWebView;
 
-    // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
-    private final Map<String, String> customHeaders = new HashMap<>();
-
     // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
     private String searchURL;
 
@@ -251,22 +246,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private ArrayList<List<String[]>> ultraList;
     private ArrayList<List<String[]>> ultraPrivacy;
 
-    // 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;
-
     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
     private ActionBarDrawerToggle actionBarDrawerToggle;
 
@@ -275,10 +254,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private ForegroundColorSpan initialGrayColorSpan;
     private ForegroundColorSpan finalGrayColorSpan;
 
-    // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
-    // and `loadBookmarksFolder()`.
-    private BookmarksDatabaseHelper bookmarksDatabaseHelper;
-
     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     private Cursor bookmarksCursor;
 
@@ -296,19 +271,39 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private int defaultProgressViewStartOffset;
     private int defaultProgressViewEndOffset;
 
-    // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
-    private boolean sanitizeGoogleAnalytics;
-    private boolean sanitizeFacebookClickIds;
-    private boolean sanitizeTwitterAmpRedirects;
+    // Declare the helpers.
+    private BookmarksDatabaseHelper bookmarksDatabaseHelper;
+    private DomainsDatabaseHelper domainsDatabaseHelper;
+    private ProxyHelper proxyHelper;
+    private SanitizeUrlHelper sanitizeUrlHelper;
+
+    // Declare the class variables
+    private boolean bottomAppBar;
+    private boolean displayAdditionalAppBarIcons;
+    private boolean displayingFullScreenVideo;
+    private boolean downloadWithExternalApp;
+    private boolean fullScreenBrowsingModeEnabled;
+    private boolean hideAppBar;
+    private boolean incognitoModeEnabled;
+    private boolean inFullScreenBrowsingMode;
+    private boolean loadingNewIntent;
+    private BroadcastReceiver orbotStatusBroadcastReceiver;
+    private boolean reapplyAppSettingsOnRestart;
+    private boolean reapplyDomainSettingsOnRestart;
+    private boolean sanitizeAmpRedirects;
+    private boolean sanitizeTrackingQueries;
+    private boolean scrollAppBar;
+    private boolean waitingForProxy;
+    private String webViewDefaultUserAgent;
 
     // Define the class variables.
-    private long lastScrollUpdate = 0;
+    private ObjectAnimator objectAnimator = new ObjectAnimator();
+    private String saveUrlString = "";
 
     // Declare the class views.
     private FrameLayout rootFrameLayout;
     private DrawerLayout drawerLayout;
-    private RelativeLayout mainContentRelativeLayout;
-    private AppBarLayout appBarLayout;
+    private CoordinatorLayout coordinatorLayout;
     private Toolbar toolbar;
     private RelativeLayout urlRelativeLayout;
     private EditText urlEditText;
@@ -371,13 +366,125 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private MenuItem optionsFontSizeMenuItem;
     private MenuItem optionsAddOrEditDomainMenuItem;
 
-    @Override
-    // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
+    // This variable won't be needed once the class is migrated to Kotlin, as can be seen in LogcatActivity or AboutVersionFragment.
+    private Activity resultLauncherActivityHandle;
+
+    // Define the save URL activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
+    private final ActivityResultLauncher<String> saveUrlActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("*/*"),
+            new ActivityResultCallback<Uri>() {
+                @Override
+                public void onActivityResult(Uri fileUri) {
+                    // Only save the URL if the file URI is not null, which happens if the user exited the file picker by pressing back.
+                    if (fileUri != null) {
+                        new SaveUrl(getApplicationContext(), resultLauncherActivityHandle, fileUri, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(saveUrlString);
+                    }
+
+                    // Reset the save URL string.
+                    saveUrlString = "";
+                }
+            });
+
+    // Define the save webpage archive activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
+    private final ActivityResultLauncher<String> saveWebpageArchiveActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("multipart/related"),
+            new ActivityResultCallback<Uri>() {
+                @Override
+                public void onActivityResult(Uri fileUri) {
+                    // Only save the webpage archive if the file URI is not null, which happens if the user exited the file picker by pressing back.
+                    if (fileUri != null) {
+                        // Initialize the file name string from the file URI last path segment.
+                        String temporaryFileNameString = fileUri.getLastPathSegment();
+
+                        // Query the exact file name if the API >= 26.
+                        if (Build.VERSION.SDK_INT >= 26) {
+                            // Get a cursor from the content resolver.
+                            Cursor contentResolverCursor = resultLauncherActivityHandle.getContentResolver().query(fileUri, null, null, null);
+
+                            // Move to the fist row.
+                            contentResolverCursor.moveToFirst();
+
+                            // Get the file name from the cursor.
+                            temporaryFileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
+
+                            // Close the cursor.
+                            contentResolverCursor.close();
+                        }
+
+                        // Save the final file name string so it can be used inside the lambdas.  This will no longer be needed once this activity has transitioned to Kotlin.
+                        String finalFileNameString = temporaryFileNameString;
+
+                        try {
+                            // Create a temporary MHT file.
+                            File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
+                            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);
+
+                                        // Get an output stream for the save webpage file path.
+                                        OutputStream mhtOutputStream = getContentResolver().openOutputStream(fileUri);
+
+                                        // 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 = 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.saved, finalFileNameString), Snackbar.LENGTH_SHORT).show();
+                                    } catch (Exception exception) {
+                                        // Display a snackbar with the exception.
+                                        Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, exception), 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, finalFileNameString, getString(R.string.unknown_error)), Snackbar.LENGTH_INDEFINITE).show();
+                                }
+                            });
+                        } catch (IOException ioException) {
+                            // Display a snackbar with the IO exception.
+                            Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, ioException), Snackbar.LENGTH_INDEFINITE).show();
+                        }
+                    }
+                }
+            });
+
+    // Define the save webpage image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
+    private final ActivityResultLauncher<String> saveWebpageImageActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("image/png"),
+            new ActivityResultCallback<Uri>() {
+                @Override
+                public void onActivityResult(Uri fileUri) {
+                    // Only save the webpage image if the file URI is not null, which happens if the user exited the file picker by pressing back.
+                    if (fileUri != null) {
+                        // Save the webpage image.
+                        new SaveWebpageImage(resultLauncherActivityHandle, fileUri, currentWebView).execute();
+                    }
+                }
+            });
+
+    // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with WebView.
     @SuppressLint("ClickableViewAccessibility")
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         // Run the default commands.
         super.onCreate(savedInstanceState);
 
+        // Populate the result launcher activity.  This will no longer be needed once the activity has transitioned to Kotlin.
+        resultLauncherActivityHandle = this;
+
         // Check to see if the activity has been restarted.
         if (savedInstanceState != null) {
             // Store the saved instance state variables.
@@ -394,9 +501,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
         // Get the preferences.
-        String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
+        String appTheme = sharedPreferences.getString(getString(R.string.app_theme_key), 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);
+        displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
 
         // Get the theme entry values string array.
         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
@@ -424,24 +532,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Enable the drawing of the entire webpage.  This makes it possible to save a website image.  This must be done before anything else happens with the WebView.
-        if (Build.VERSION.SDK_INT >= 21) {
-            WebView.enableSlowWholeDocumentDraw();
-        }
+        WebView.enableSlowWholeDocumentDraw();
 
-        // Set the theme.
-        setTheme(R.style.PrivacyBrowser);
-
-        // Set the content view.
-        if (bottomAppBar) {
-            setContentView(R.layout.main_framelayout_bottom_appbar);
-        } else {
-            setContentView(R.layout.main_framelayout_top_appbar);
-        }
+        // Set the content view according to the position of the app bar.
+        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);
+        coordinatorLayout = findViewById(R.id.coordinatorlayout);
         appBarLayout = findViewById(R.id.appbar_layout);
         toolbar = findViewById(R.id.toolbar);
         findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
@@ -449,10 +549,8 @@ 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);
+        fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
 
         // Get a handle for the navigation menu.
         Menu navigationMenu = navigationView.getMenu();
@@ -492,6 +590,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
 
+        // Initially hide the user interface so that only the blocklist loading screen is shown (if reloading).
+        drawerLayout.setVisibility(View.GONE);
+
         // Initialize the web view pager adapter.
         webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
 
@@ -501,12 +602,60 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Store up to 100 tabs in memory.
         webViewPager.setOffscreenPageLimit(100);
 
+        // Instantiate the helpers.
+        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
+        domainsDatabaseHelper = new DomainsDatabaseHelper(this);
+        proxyHelper = new ProxyHelper();
+        sanitizeUrlHelper = new SanitizeUrlHelper();
+
         // Initialize the app.
         initializeApp();
 
         // Apply the app settings from the shared preferences.
         applyAppSettings();
 
+        // Control what the system back command does.
+        OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
+            @Override
+            public void handleOnBackPressed() {
+                // Process the different back options.
+                if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
+                    // Close the navigation drawer.
+                    drawerLayout.closeDrawer(GravityCompat.START);
+                } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
+                    // close the bookmarks drawer.
+                    drawerLayout.closeDrawer(GravityCompat.END);
+                } else if (displayingFullScreenVideo) {  // A full screen video is shown.
+                    // 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();
+
+                    // Get the previous entry URL.
+                    String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
+
+                    // Apply the domain settings.
+                    applyDomainSettings(currentWebView, previousUrl, false, false, false);
+
+                    // Go back.
+                    currentWebView.goBack();
+                } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
+                    // Close the current tab.
+                    closeCurrentTab();
+                } else {  // There isn't anything to do in Privacy Browser.
+                    // Close Privacy Browser.  `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
+                    finishAndRemoveTask();
+
+                    // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
+                    System.exit(0);
+                }
+            }
+        };
+
+        // Register the on back pressed callback.
+        getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
+
         // Populate the blocklists.
         populateBlocklists = new PopulateBlocklists(this, this).execute();
     }
@@ -516,9 +665,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the default commands.
         super.onNewIntent(intent);
 
-        // Replace the intent that started the app with this one.
-        setIntent(intent);
-
         // Check to see if the app is being restarted from a saved state.
         if (savedStateArrayList == null || savedStateArrayList.size() == 0) {  // The activity is not being restarted from a saved state.
             // Get the information from the intent.
@@ -569,7 +715,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Add a new tab if specified in the preferences.
-                if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
+                if (sharedPreferences.getBoolean(getString(R.string.open_intents_in_new_tab_key), true)) {  // Load the URL in a new tab.
                     // Set the loading new intent flag.
                     loadingNewIntent = true;
 
@@ -590,6 +736,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     drawerLayout.closeDrawer(GravityCompat.END);
                 }
             }
+        } else {  // The app has been restarted.
+            // Replace the intent that started the app with this one.  This will load the tab after the others have been restored.
+            setIntent(intent);
         }
     }
 
@@ -626,7 +775,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
                     // Reset the current domain name so the domain settings will be reapplied.
-                    nestedScrollWebView.resetCurrentDomainName();
+                    nestedScrollWebView.setCurrentDomainName("");
 
                     // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
                     if (nestedScrollWebView.getUrl() != null) {
@@ -686,7 +835,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             applyProxy(false);
         }
 
-        // Reapply any system UI flags and the ad in the free flavor.
+        // Reapply any system UI flags.
         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {  // The system is displaying a website or a video in full screen mode.
             /* Hide the system bars.
              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
@@ -696,13 +845,19 @@ 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(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.
@@ -732,15 +887,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         if (currentWebView != null) {
             currentWebView.pauseTimers();
         }
-
-        // 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(adView);
-        }
     }
 
     @Override
@@ -821,9 +967,10 @@ 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;
 
-        // Get handles for the class menu items.
+        // Get handles for the menu items.
         optionsPrivacyMenuItem = menu.findItem(R.id.javascript);
         optionsRefreshMenuItem = menu.findItem(R.id.refresh);
+        MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks);
         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.
@@ -865,10 +1012,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         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 adConsentMenuItem = menu.findItem(R.id.ad_consent);
-
         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
         updatePrivacyIcons(false);
 
@@ -879,18 +1022,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
         optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
 
-        // Only display the dark WebView menu item if API >= 21.
-        optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
-
-        // Only show Ad Consent if this is the free flavor.
-        adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
-
-        // Get the shared preferences.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
-
-        // Get the dark theme and app bar preferences.
-        boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
-
         // 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) {
             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
@@ -907,17 +1038,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Set the title.
             optionsRefreshMenuItem.setTitle(R.string.stop);
 
-            // Set the icon if it is displayed in the app bar.
+            // Set the icon if it is displayed in the app bar.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
             if (displayAdditionalAppBarIcons) {
-                // Get the current theme status.
-                int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-                // Set the icon according to the current theme status.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
-                } else {
-                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
-                }
+                optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
             }
         }
 
@@ -952,13 +1075,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Set the status of the menu item checkboxes.
             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));
+            optionsEasyListMenuItem.setChecked(currentWebView.getEasyListEnabled());
+            optionsEasyPrivacyMenuItem.setChecked(currentWebView.getEasyPrivacyEnabled());
+            optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.getFanboysAnnoyanceListEnabled());
+            optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.getFanboysSocialBlockingListEnabled());
+            optionsUltraListMenuItem.setChecked(currentWebView.getUltraListEnabled());
+            optionsUltraPrivacyMenuItem.setChecked(currentWebView.getUltraPrivacyEnabled());
+            optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.getBlockAllThirdPartyRequests());
             optionsSwipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
             optionsWideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
             optionsDisplayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
@@ -976,8 +1099,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Enable DOM Storage if JavaScript is enabled.
             optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
 
+            // Get the current theme status.
+            int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+            // Enable dark WebView if the API is < 33 or if night mode is enabled.
+            optionsDarkWebViewMenuItem.setEnabled((Build.VERSION.SDK_INT < 33) || (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES));
+
             // Set the checkbox status for dark WebView if the WebView supports it.
-            if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
+            if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
+                optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings()));
+            } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
+                //noinspection deprecation
                 optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
             }
         }
@@ -1283,12 +1415,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         @Override
                         public void onDismissed(Snackbar snackbar, int event) {
                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
-                                // Delete the cookies, which command varies by SDK.
-                                if (Build.VERSION.SDK_INT < 21) {
-                                    cookieManager.removeAllCookie();
-                                } else {
-                                    cookieManager.removeAllCookies(null);
-                                }
+                                // Delete the cookies.
+                                cookieManager.removeAllCookies(null);
                             }
                         }
                     })
@@ -1376,10 +1504,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.easylist) {  // EasyList.
             // Toggle the EasyList status.
-            currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+            currentWebView.setEasyListEnabled(!currentWebView.getEasyListEnabled());
 
             // Update the menu checkbox.
-            menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+            menuItem.setChecked(currentWebView.getEasyListEnabled());
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1388,10 +1516,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.easyprivacy) {  // EasyPrivacy.
             // Toggle the EasyPrivacy status.
-            currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+            currentWebView.setEasyPrivacyEnabled(!currentWebView.getEasyPrivacyEnabled());
 
             // Update the menu checkbox.
-            menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+            menuItem.setChecked(currentWebView.getEasyPrivacyEnabled());
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1400,13 +1528,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.fanboys_annoyance_list) {  // Fanboy's Annoyance List.
             // Toggle Fanboy's Annoyance List status.
-            currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+            currentWebView.setFanboysAnnoyanceListEnabled(!currentWebView.getFanboysAnnoyanceListEnabled());
 
             // Update the menu checkbox.
-            menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+            menuItem.setChecked(currentWebView.getFanboysAnnoyanceListEnabled());
 
             // Update the status of Fanboy's Social Blocking List.
-            optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+            optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.getFanboysAnnoyanceListEnabled());
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1415,10 +1543,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.fanboys_social_blocking_list) {  // Fanboy's Social Blocking List.
             // Toggle Fanboy's Social Blocking List status.
-            currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+            currentWebView.setFanboysSocialBlockingListEnabled(!currentWebView.getFanboysSocialBlockingListEnabled());
 
             // Update the menu checkbox.
-            menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+            menuItem.setChecked(currentWebView.getFanboysSocialBlockingListEnabled());
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1427,10 +1555,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.ultralist) {  // UltraList.
             // Toggle the UltraList status.
-            currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+            currentWebView.setUltraListEnabled(!currentWebView.getUltraListEnabled());
 
             // Update the menu checkbox.
-            menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+            menuItem.setChecked(currentWebView.getUltraListEnabled());
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1439,10 +1567,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.ultraprivacy) {  // UltraPrivacy.
             // Toggle the UltraPrivacy status.
-            currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+            currentWebView.setUltraPrivacyEnabled(!currentWebView.getUltraPrivacyEnabled());
 
             // Update the menu checkbox.
-            menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+            menuItem.setChecked(currentWebView.getUltraPrivacyEnabled());
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1451,10 +1579,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.block_all_third_party_requests) {  // Block all third-party requests.
             //Toggle the third-party requests blocker status.
-            currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+            currentWebView.setBlockAllThirdPartyRequests(!currentWebView.getBlockAllThirdPartyRequests());
 
             // Update the menu checkbox.
-            menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+            menuItem.setChecked(currentWebView.getBlockAllThirdPartyRequests());
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1607,7 +1735,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.user_agent_custom) {  // User Agent - Custom.
             // Update the user agent.
-            currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
+            currentWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value)));
 
             // Reload the current WebView.
             currentWebView.reload();
@@ -1661,13 +1789,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             return true;
         } else if (menuItemId == R.id.dark_webview) {  // Dark WebView.
             // Check to see if dark WebView is supported by this WebView.
-            if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
+            if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
+                // Toggle algorithmic darkening.
+                WebSettingsCompat.setAlgorithmicDarkeningAllowed(currentWebView.getSettings(), !WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings()));
+            } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
                 // Toggle the dark WebView setting.
+                //noinspection deprecation
                 if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) {  // Dark WebView is currently enabled.
                     // Turn off dark WebView.
+                    //noinspection deprecation
                     WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
                 } else {  // Dark WebView is currently disabled.
                     // Turn on dark WebView.
+                    //noinspection deprecation
                     WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                 }
             }
@@ -1715,7 +1849,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             assert printManager != null;
 
             // Create a print document adapter from the current WebView.
-            PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
+            PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(getString(R.string.print));
 
             // Print the document.
             printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
@@ -1728,28 +1862,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 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(),
+                new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
                         currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
             }
 
             // Consume the event.
             return true;
         } else if (menuItemId == R.id.save_archive) {
-            // Instantiate the save dialog.
-            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));
+            // Open the file picker with a default file name built from the current domain name.
+            saveWebpageArchiveActivityResultLauncher.launch(currentWebView.getCurrentDomainName() + ".mht");
 
+            // Consume the event.
             return true;
         } else if (menuItemId == R.id.save_image) {  // Save image.
-            // Instantiate the save dialog.
-            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.
-            saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+            // Open the file picker with a default file name built from the current domain name.
+            saveWebpageImageActivityResultLauncher.launch(currentWebView.getCurrentDomainName() + ".png");
 
             // Consume the event.
             return true;
@@ -1776,24 +1903,45 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Consume the event.
             return true;
-        } else if (menuItemId == R.id.share_url) {  // Share URL.
-            // Setup the share string.
+        } else if (menuItemId == R.id.share_message) {  // Share a message.
+            // Prepare the share string.
             String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
 
             // Create the share intent.
-            Intent shareIntent = new Intent(Intent.ACTION_SEND);
+            Intent shareMessageIntent = new Intent(Intent.ACTION_SEND);
 
             // Add the share string to the intent.
-            shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
+            shareMessageIntent.putExtra(Intent.EXTRA_TEXT, shareString);
 
             // Set the MIME type.
-            shareIntent.setType("text/plain");
+            shareMessageIntent.setType("text/plain");
 
             // Set the intent to open in a new task.
-            shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            shareMessageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
             // Make it so.
-            startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
+            startActivity(Intent.createChooser(shareMessageIntent, getString(R.string.share_message)));
+
+            // Consume the event.
+            return true;
+        } else if (menuItemId == R.id.share_url) {  // Share URL.
+            // Create the share intent.
+            Intent shareUrlIntent = new Intent(Intent.ACTION_SEND);
+
+            // Add the URL to the intent.
+            shareUrlIntent.putExtra(Intent.EXTRA_TEXT, currentWebView.getUrl());
+
+            // Add the title to the intent.
+            shareUrlIntent.putExtra(Intent.EXTRA_SUBJECT, currentWebView.getTitle());
+
+            // Set the MIME type.
+            shareUrlIntent.setType("text/plain");
+
+            // Set the intent to open in a new task.
+            shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            //Make it so.
+            startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)));
 
             // Consume the event.
             return true;
@@ -1810,18 +1958,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Consume the event.
             return true;
         } else if (menuItemId == R.id.add_or_edit_domain) {  // Add or edit domain.
+            // Reapply the domain settings on returning to `MainWebViewActivity`.
+            reapplyDomainSettingsOnRestart = true;
+
             // Check if domain settings currently exist.
             if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
-                // Reapply the domain settings on returning to `MainWebViewActivity`.
-                reapplyDomainSettingsOnRestart = true;
-
                 // Create an intent to launch the domains activity.
                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
                 // Add the extra information to the intent.
-                domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
-                domainsIntent.putExtra("close_on_back", true);
-                domainsIntent.putExtra("current_url", currentWebView.getUrl());
+                domainsIntent.putExtra(DomainsActivity.LOAD_DOMAIN, currentWebView.getDomainSettingsDatabaseId());
+                domainsIntent.putExtra(DomainsActivity.CLOSE_ON_BACK, true);
+                domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl());
+                domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses());
 
                 // Get the current certificate.
                 SslCertificate sslCertificate = currentWebView.getCertificate();
@@ -1839,34 +1988,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
 
                     // Add the certificate to the intent.
-                    domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
-                    domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
-                    domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
-                    domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
-                    domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
-                    domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
-                    domainsIntent.putExtra("ssl_start_date", startDateLong);
-                    domainsIntent.putExtra("ssl_end_date", endDateLong);
-                }
-
-                // Check to see if the current IP addresses have been received.
-                if (currentWebView.hasCurrentIpAddresses()) {
-                    // Add the current IP addresses to the intent.
-                    domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_CNAME, issuedToCName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_ONAME, issuedToOName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_UNAME, issuedToUName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_CNAME, issuedByCName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_ONAME, issuedByOName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_UNAME, issuedByUName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_START_DATE, startDateLong);
+                    domainsIntent.putExtra(DomainsActivity.SSL_END_DATE, endDateLong);
                 }
 
                 // Make it so.
                 startActivity(domainsIntent);
             } else {  // Add a new domain.
-                // Apply the new domain settings on returning to `MainWebViewActivity`.
-                reapplyDomainSettingsOnRestart = true;
-
-                // Get the current domain
+                // Get the current URI.
                 Uri currentUri = Uri.parse(currentWebView.getUrl());
+
+                // Get the current domain from the URI.
                 String currentDomain = currentUri.getHost();
 
-                // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-                DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+                // Set an empty domain if it is null.
+                if (currentDomain == null)
+                    currentDomain = "";
 
                 // Create the domain and store the database ID.
                 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
@@ -1875,9 +2018,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
                 // Add the extra information to the intent.
-                domainsIntent.putExtra("load_domain", newDomainDatabaseId);
-                domainsIntent.putExtra("close_on_back", true);
-                domainsIntent.putExtra("current_url", currentWebView.getUrl());
+                domainsIntent.putExtra(DomainsActivity.LOAD_DOMAIN, newDomainDatabaseId);
+                domainsIntent.putExtra(DomainsActivity.CLOSE_ON_BACK, true);
+                domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl());
+                domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses());
 
                 // Get the current certificate.
                 SslCertificate sslCertificate = currentWebView.getCertificate();
@@ -1895,35 +2039,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
 
                     // Add the certificate to the intent.
-                    domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
-                    domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
-                    domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
-                    domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
-                    domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
-                    domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
-                    domainsIntent.putExtra("ssl_start_date", startDateLong);
-                    domainsIntent.putExtra("ssl_end_date", endDateLong);
-                }
-
-                // Check to see if the current IP addresses have been received.
-                if (currentWebView.hasCurrentIpAddresses()) {
-                    // Add the current IP addresses to the intent.
-                    domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_CNAME, issuedToCName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_ONAME, issuedToOName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_TO_UNAME, issuedToUName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_CNAME, issuedByCName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_ONAME, issuedByOName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_ISSUED_BY_UNAME, issuedByUName);
+                    domainsIntent.putExtra(DomainsActivity.SSL_START_DATE, startDateLong);
+                    domainsIntent.putExtra(DomainsActivity.SSL_END_DATE, endDateLong);
                 }
 
                 // Make it so.
                 startActivity(domainsIntent);
             }
 
-            // Consume the event.
-            return true;
-        } else if (menuItemId == R.id.ad_consent) {  // Ad consent.
-            // Instantiate the ad consent dialog.
-            DialogFragment adConsentDialogFragment = new AdConsentDialog();
-
-            // Display the ad consent dialog.
-            adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
-
             // Consume the event.
             return true;
         } else {  // There is no match with the options menu.  Pass the event up to the parent method.
@@ -1998,7 +2127,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             Intent requestsIntent = new Intent(this, RequestsActivity.class);
 
             // Add the block third-party requests status to the intent.
-            requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+            requestsIntent.putExtra("block_all_third_party_requests", currentWebView.getBlockAllThirdPartyRequests());
 
             // Make it so.
             startActivity(requestsIntent);
@@ -2055,7 +2184,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             Intent domainsIntent = new Intent(this, DomainsActivity.class);
 
             // Add the extra information to the intent.
-            domainsIntent.putExtra("current_url", currentWebView.getUrl());
+            domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl());
+            domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses());
 
             // Get the current certificate.
             SslCertificate sslCertificate = currentWebView.getCertificate();
@@ -2083,12 +2213,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 domainsIntent.putExtra("ssl_end_date", endDateLong);
             }
 
-            // Check to see if the current IP addresses have been received.
-            if (currentWebView.hasCurrentIpAddresses()) {
-                // Add the current IP addresses to the intent.
-                domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
-            }
-
             // Make it so.
             startActivity(domainsIntent);
         } else if (menuItemId == R.id.settings) {  // Settings.
@@ -2113,6 +2237,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Make it so.
             startActivity(logcatIntent);
+        } else if (menuItemId == R.id.webview_devtools) {  // WebView Dev.
+            // Create a WebView DevTools intent.
+            Intent webViewDevToolsIntent = new Intent("com.android.webview.SHOW_DEV_UI");
+
+            // Launch as a new task so that the WebView DevTools and Privacy Browser show as a separate windows in the recent tasks list.
+            webViewDevToolsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            // Make it so.
+            startActivity(webViewDevToolsIntent);
         } else if (menuItemId == R.id.guide) {  // Guide.
             // Create an intent to launch the guide activity.
             Intent guideIntent = new Intent(this, GuideActivity.class);
@@ -2148,26 +2281,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         actionBarDrawerToggle.syncState();
     }
 
-    @Override
-    public void onConfigurationChanged(@NonNull Configuration newConfig) {
-        // Run the default commands.
-        super.onConfigurationChanged(newConfig);
-
-        // 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.
-            // `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:
-        // https://code.google.com/p/android/issues/detail?id=20493#c8
-        // ActivityCompat.invalidateOptionsMenu(this);
-    }
-
     @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
         // Get the hit test result.
@@ -2246,7 +2359,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         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(),
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
                                 currentWebView.getAcceptCookies()).execute(linkUrl);
                     }
 
@@ -2318,7 +2431,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         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(),
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
                                 currentWebView.getAcceptCookies()).execute(imageUrl);
                     }
 
@@ -2423,7 +2536,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         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(),
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
                                 currentWebView.getAcceptCookies()).execute(imageUrl);
                     }
 
@@ -2450,7 +2563,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         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(),
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
                                 currentWebView.getAcceptCookies()).execute(linkUrl);
                     }
 
@@ -2612,7 +2725,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Update the bookmarks cursor with the current contents of this folder.
         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-        // Update the `ListView`.
+        // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
 
         // Scroll to the new folder.
@@ -2706,51 +2819,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Update the bookmarks cursor with the current contents of this folder.
         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-        // Update the `ListView`.
+        // Update the list view.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
     }
 
-    // Override `onBackPressed()` to handle the navigation drawer and and the WebViews.
-    @Override
-    public void onBackPressed() {
-        // Check the different options for processing `back`.
-        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
-            // Close the navigation drawer.
-            drawerLayout.closeDrawer(GravityCompat.START);
-        } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
-            // close the bookmarks drawer.
-            drawerLayout.closeDrawer(GravityCompat.END);
-        } else if (displayingFullScreenVideo) {  // A full screen video is shown.
-            // 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();
-
-            // Get the previous entry URL.
-            String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
-
-            // Apply the domain settings.
-            applyDomainSettings(currentWebView, previousUrl, false, false, false);
-
-            // Go back.
-            currentWebView.goBack();
-        } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
-            // Close the current tab.
-            closeCurrentTab();
-        } else {  // There isn't anything to do in Privacy Browser.
-            // Close Privacy Browser.  `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
-            if (Build.VERSION.SDK_INT >= 21) {
-                finishAndRemoveTask();
-            } else {
-                finish();
-            }
-
-            // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
-            System.exit(0);
-        }
-    }
-
     // Process the results of a file browse.
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
@@ -2760,11 +2832,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the commands that correlate to the specified request code.
         switch (requestCode) {
             case BROWSE_FILE_UPLOAD_REQUEST_CODE:
-                // File uploads only work on API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Pass the file to the WebView.
-                    fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
-                }
+                // Pass the file to the WebView.
+                fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
                 break;
 
             case BROWSE_OPEN_REQUEST_CODE:
@@ -2798,38 +2867,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     }
                 }
                 break;
-
-            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 save dialog fragment.
-                    DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
-
-                    // 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();
-
-                        // Remove the incorrect lint warning below that the dialog might be null.
-                        assert saveWebpageDialog != null;
-
-                        // Get a handle for the file name edit text.
-                        EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
-
-                        // Get the file name URI from the intent.
-                        Uri fileNameUri = returnedIntent.getData();
-
-                        // Get the file name string from the URI.
-                        String fileNameString = fileNameUri.toString();
-
-                        // Set the file name text.
-                        fileNameEditText.setText(fileNameString);
-
-                        // Move the cursor to the end of the file name edit text.
-                        fileNameEditText.setSelection(fileNameString.length());
-                    }
-                }
-                break;
         }
     }
 
@@ -2840,21 +2877,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Initialize the formatted URL string.
         String url = "";
 
-        // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
+        // Check to see if the unformatted URL string is a valid URL.  Otherwise, convert it into a search.
         if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
             // Load the entire content URL.
             url = unformattedUrlString;
         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
                 unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
-            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
+            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://")) {
                 unformattedUrlString = "https://" + unformattedUrlString;
             }
 
-            // Initialize `unformattedUrl`.
+            // Initialize the unformatted URL.
             URL unformattedUrl = null;
 
-            // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
+            // Convert the unformatted URL string to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components.
             try {
                 unformattedUrl = new URL(unformattedUrlString);
             } catch (MalformedURLException e) {
@@ -3028,7 +3065,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 currentWebView.loadUrl(temporaryMhtFile.toString());
             } catch (Exception exception) {
                 // Display a snackbar.
-                Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+                Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
             }
         } else {  // Let the WebView handle opening of the file.
             // Open the file.
@@ -3050,97 +3087,27 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         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 file name edit text.
-        EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
-
-        // Get the file path from the edit text.
-        String saveWebpageFilePath = fileNameEditText.getText().toString();
-
-        //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);
-
-                // Define the save webpage URL.
-                String saveWebpageUrl;
-
-                // 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();
-                }
-
-                // Save the URL.
-                new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(saveWebpageUrl);
-                break;
-
-            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);
-
-                                // 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;
+    public void onSaveUrl(@NonNull String originalUrlString, @NonNull String fileNameString, @NonNull DialogFragment dialogFragment) {
+        // Store the URL.  This will be used in the save URL activity result launcher.
+        if (originalUrlString.startsWith("data:")) {
+            // Save the original URL.
+            saveUrlString = originalUrlString;
+        } else {
+            // Get the dialog.
+            Dialog dialog = dialogFragment.getDialog();
 
-                                // Copy the temporary MHT file input stream to the MHT output stream.
-                                while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) {
-                                    mhtOutputStream.write(transferByteArray, 0, bytesRead);
-                                }
+            // Remove the incorrect lint warning below that the dialog might be null.
+            assert dialog != null;
 
-                                // 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;
+            // Get a handle for the dialog URL edit text.
+            EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext);
 
-            case SaveWebpageDialog.SAVE_IMAGE:
-                // Save the webpage image.
-                new SaveWebpageImage(this, saveWebpageFilePath, currentWebView).execute();
-                break;
+            // Get the URL from the edit text, which may have been modified.
+            saveUrlString = dialogUrlEditText.getText().toString();
         }
+
+        // Open the file picker.
+        saveUrlActivityResultLauncher.launch(fileNameString);
     }
     
     // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
@@ -3152,19 +3119,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Remove the lint warning below that the input method manager might be null.
         assert inputMethodManager != null;
 
-        // Initialize the gray foreground color spans for highlighting the URLs.  The deprecated `getResources()` must be used until API >= 23.
-        initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
-        finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
-
-        // Get the current theme status.
-        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-        // Set the red color span according to the theme.
-        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
-        } else {
-            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
-        }
+        // Initialize the color spans for highlighting the URLs.
+        initialGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500));
+        finalGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500));
+        redColorSpan = new ForegroundColorSpan(getColor(R.color.red_text));
 
         // Remove the formatting from the URL edit text when the user is editing the text.
         urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
@@ -3205,16 +3163,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
 
                 // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
-                if ((orbotStatus != null) && orbotStatus.equals("ON") && waitingForProxy) {
+                if ((orbotStatus != null) && orbotStatus.equals(ProxyHelper.ORBOT_STATUS_ON) && waitingForProxy) {
                     // 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();
 
-                    // Dismiss the waiting for proxy dialog if it is displayed.
-                    if (waitingForProxyDialogFragment != null) {
-                        waitingForProxyDialogFragment.dismiss();
+                    // 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();
+
+                        // 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.
@@ -3239,7 +3204,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 loadUrl(nestedScrollWebView, waitingForProxyUrlString);
 
                                 // Reset the waiting for proxy URL string.
-                                nestedScrollWebView.resetWaitingForProxyUrlString();
+                                nestedScrollWebView.setWaitingForProxyUrlString("");
                             } else {  // No URL is waiting to be loaded.
                                 // Reload the existing URL.
                                 nestedScrollWebView.reload();
@@ -3314,7 +3279,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             @Override
             public void onTabReselected(TabLayout.Tab tab) {
                 // Instantiate the View SSL Certificate dialog.
-                DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
+                DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId(), currentWebView.getFavoriteOrDefaultIcon());
 
                 // Display the View SSL Certificate dialog.
                 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
@@ -3409,17 +3374,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Implement swipe to refresh.
         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();
-            }
+            // Reload the website.
+            currentWebView.reload();
         });
 
         // Store the default progress view offsets for use later in `initializeWebView()`.
@@ -3427,11 +3383,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
 
         // Set the refresh color scheme according to the theme.
-        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-            swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
-        } else {
-            swipeRefreshLayout.setColorSchemeResources(R.color.violet_500);
-        }
+        swipeRefreshLayout.setColorSchemeResources(R.color.blue_text);
 
         // Initialize a color background typed value.
         TypedValue colorBackgroundTypedValue = new TypedValue();
@@ -3449,15 +3401,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
 
-        // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
-        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
-
-        // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
-        currentBookmarksFolder = "";
-
-        // Load the home folder, which is `""` in the database.
+        // Load the bookmarks folder.
         loadBookmarksFolder();
 
+        // Handle clicks on bookmarks.
         bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
             // Convert the id from long to int to match the format of the bookmarks database.
             int databaseId = (int) id;
@@ -3469,15 +3416,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             bookmarkCursor.moveToFirst();
 
             // Act upon the bookmark according to the type.
-            if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
+            if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
                 // Store the new folder name in `currentBookmarksFolder`.
-                currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+                currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
 
                 // Load the new folder.
                 loadBookmarksFolder();
             } else {  // The selected bookmark is not a folder.
                 // Load the bookmark URL.
-                loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
+                loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)));
 
                 // Close the bookmarks drawer.
                 drawerLayout.closeDrawer(GravityCompat.END);
@@ -3497,7 +3444,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // 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));
+                oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
 
                 // Instantiate the edit folder bookmark dialog.
                 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
@@ -3511,11 +3458,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Move the bookmark cursor to the first row.
                 bookmarkCursor.moveToFirst();
 
-                // 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);
+                // Load the bookmark in a new tab.
+                addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), true);
 
-                // Display a snackbar.
-                Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
+                // Close the bookmarks drawer.
+                drawerLayout.closeDrawer(GravityCompat.END);
             }
 
             // Consume the event.
@@ -3552,16 +3499,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
                     }
 
-                    // Clear the focus from from the URL text box and the WebView.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
+                    // Clear the focus from from the URL text box.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
                     urlEditText.clearFocus();
-                    currentWebView.clearFocus();
+
+                    // Clear the focus from from the WebView if it is not null, which can happen if a user opens a drawer while the browser is being resumed.
+                    if (currentWebView != null) {
+                        // Clearing the focus from the WebView removes any text selection markers and context menus, which otherwise draw above the open drawers.
+                        currentWebView.clearFocus();
+                    }
                 }
             }
         });
 
-        // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
-        customHeaders.put("X-Requested-With", "");
-
         // Inflate a bare WebView to get the default user agent.  It is not used to render content on the screen.
         @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
 
@@ -3580,15 +3529,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
         // Store the values from the shared preferences in variables.
-        incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", 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);
+        incognitoModeEnabled = sharedPreferences.getBoolean(getString(R.string.incognito_mode_key), false);
+        sanitizeTrackingQueries = sharedPreferences.getBoolean(getString(R.string.tracking_queries_key), true);
+        sanitizeAmpRedirects = sharedPreferences.getBoolean(getString(R.string.amp_redirects_key), true);
+        proxyMode = sharedPreferences.getString(getString(R.string.proxy_key), getString(R.string.proxy_default_value));
+        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean(getString(R.string.full_screen_browsing_mode_key), false);
+        hideAppBar = sharedPreferences.getBoolean(getString(R.string.hide_app_bar_key), true);
         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);
+        scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), true);
 
         // Apply the saved proxy mode if the app has been restarted.
         if (savedProxyMode != null) {
@@ -3600,20 +3548,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Get the search string.
-        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
+        String searchString = sharedPreferences.getString(getString(R.string.search_key), getString(R.string.search_default_value));
 
         // Set the search string.
-        if (searchString.equals("Custom URL")) {  // A custom search string is used.
-            searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
-        } else {  // A custom search string is not used.
+        if (searchString.equals(getString(R.string.custom_url_item)))
+            searchURL = sharedPreferences.getString(getString(R.string.search_custom_url_key), getString(R.string.search_custom_url_default_value));
+        else
             searchURL = searchString;
-        }
 
         // Apply the proxy.
         applyProxy(false);
 
-        // Adjust the layout and scrolling parameters if the app bar is at the top of the screen.
-        if (!bottomAppBar) {
+        // Adjust the layout and scrolling parameters according to the position of the app bar.
+        if (bottomAppBar) {  // The app bar is on the bottom.
+            // Adjust the UI.
+            if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
+                // Reset the WebView padding to fill the available space.
+                swipeRefreshLayout.setPadding(0, 0, 0, 0);
+            } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
+                // Move the WebView above the app bar layout.
+                swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
+
+                // Show the app bar if it is scrolled off the screen.
+                if (appBarLayout.getTranslationY() != 0) {
+                    // Animate the bottom app bar onto the screen.
+                    objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
+
+                    // Make it so.
+                    objectAnimator.start();
+                }
+            }
+        } else {  // The app bar is on the top.
             // 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();
@@ -3674,15 +3639,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 actionBar.show();
             }
 
-            // 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);
-
-                // Hide the banner ad.
-                AdHelper.hideAd(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.
@@ -3701,16 +3657,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Show the action bar.
             actionBar.show();
 
-            // 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.
-                // `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.
             rootFrameLayout.setSystemUiVisibility(0);
         }
@@ -3767,7 +3713,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Clear any pinned SSL certificate or IP addresses.
             nestedScrollWebView.clearPinnedSslCertificate();
-            nestedScrollWebView.clearPinnedIpAddresses();
+            nestedScrollWebView.setPinnedIpAddresses("");
 
             // Reset the favorite icon if specified.
             if (resetTab) {
@@ -3800,28 +3746,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
             }
 
-            // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
-
-            // Get a full cursor from `domainsDatabaseHelper`.
+            // Get a full domain name cursor.
             Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
 
             // Initialize `domainSettingsSet`.
             Set<String> domainSettingsSet = new HashSet<>();
 
             // Get the domain name column index.
-            int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
+            int domainNameColumnIndex = domainNameCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME);
 
-            // Populate `domainSettingsSet`.
+            // Populate the domain settings set.
             for (int i = 0; i < domainNameCursor.getCount(); i++) {
-                // Move `domainsCursor` to the current row.
+                // Move the domains cursor to the current row.
                 domainNameCursor.moveToPosition(i);
 
-                // Store the domain name in `domainSettingsSet`.
+                // Store the domain name in the domain settings set.
                 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
             }
 
-            // Close `domainNameCursor.
+            // Close the domain name cursor.
             domainNameCursor.close();
 
             // Initialize the domain name in database variable.
@@ -3840,7 +3783,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
 
             // Check all the subdomains of the host name against wildcard domains in the domain cursor.
-            while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the host name.
+            while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the hostname.
                 if (domainSettingsSet.contains("*." + newHostName)) {  // Check the host name prepended by `*.`.
                     // Set the domain settings applied tracker to true.
                     nestedScrollWebView.setDomainSettingsApplied(true);
@@ -3858,12 +3801,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
             // Store the general preference information.
-            String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
-            String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
-            boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
-            String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
-            boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
-            boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
+            String defaultFontSizeString = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value));
+            String defaultUserAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value));
+            boolean defaultSwipeToRefresh = sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true);
+            String webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value));
+            boolean wideViewport = sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true);
+            boolean displayWebpageImages = sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true);
 
             // Get the WebView theme entry values string array.
             String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values);
@@ -3876,67 +3819,49 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
 
             if (nestedScrollWebView.getDomainSettingsApplied()) {  // The url has custom domain settings.
-                // Get a cursor for the current host and move it to the first position.
+                // Remove the incorrect lint warning below that the domain name in database might be null.
+                assert domainNameInDatabase != null;
+
+                // Get a cursor for the current host.
                 Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
+
+                // Move to the first position.
                 currentDomainSettingsCursor.moveToFirst();
 
                 // 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.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.COOKIES)) == 1);
-                nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
+                nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID)));
+                nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
+                nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)) == 1);
+                nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(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);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST,
-                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY,
-                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST,
-                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST,
-                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY,
-                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS,
-                        currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
-                String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
-                int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
-                int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
-                int webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WEBVIEW_THEME));
-                int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT));
-                int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
-                boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
-                String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
-                String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
-                String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
-                String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
-                String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
-                String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
-                boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
-                String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
-
-                // Get the pinned SSL date longs.
-                long pinnedSslStartDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE));
-                long pinnedSslEndDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE));
-
-                // Define the pinned SSL date variables.
-                Date pinnedSslStartDate;
-                Date pinnedSslEndDate;
-
-                // Set the pinned SSL certificate start date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0.
-                if (pinnedSslStartDateLong == 0) {
-                    pinnedSslStartDate = null;
-                } else {
-                    pinnedSslStartDate = new Date(pinnedSslStartDateLong);
-                }
-
-                // Set the pinned SSL certificate end date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0.
-                if (pinnedSslEndDateLong == 0) {
-                    pinnedSslEndDate = null;
-                } else {
-                    pinnedSslEndDate = new Date(pinnedSslEndDateLong);
-                }
+                boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
+                nestedScrollWebView.setEasyListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
+                nestedScrollWebView.setEasyPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
+                nestedScrollWebView.setFanboysAnnoyanceListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
+                        DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
+                nestedScrollWebView.setFanboysSocialBlockingListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
+                        DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
+                nestedScrollWebView.setUltraListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)) == 1);
+                nestedScrollWebView.setUltraPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
+                nestedScrollWebView.setBlockAllThirdPartyRequests(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
+                        DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
+                String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT));
+                int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE));
+                int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
+                int webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME));
+                int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT));
+                int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES));
+                boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
+                String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
+                String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
+                String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
+                String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
+                String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
+                String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+                Date pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)));
+                Date pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)));
+                boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
+                String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES));
 
                 // Close the current host domain settings cursor.
                 currentDomainSettingsCursor.close();
@@ -3992,7 +3917,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         case SETTINGS_CUSTOM_USER_AGENT:
                             // Set the default custom user agent.
-                            nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
+                            nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value)));
                             break;
 
                         default:
@@ -4049,19 +3974,52 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // Disable swipe to refresh.
                         swipeRefreshLayout.setEnabled(false);
+                        break;
                 }
 
                 // Check to see if WebView themes are supported.
-                if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
+                if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
+                    // Set the WebView theme.
+                    switch (webViewThemeInt) {
+                        case DomainsDatabaseHelper.SYSTEM_DEFAULT:
+                            // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
+                            if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
+                                // Turn off algorithmic darkening.
+                                WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
+                            } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
+                                // Turn on algorithmic darkening.
+                                WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
+                            } else {  // The system default theme is selected.
+                                // Get the current system theme status.
+                                int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+                                // Set the algorithmic darkening according to the current system theme status.
+                                WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES));
+                            }
+                            break;
+
+                        case DomainsDatabaseHelper.LIGHT_THEME:
+                            // Turn off algorithmic darkening.
+                            WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
+                            break;
+
+                        case DomainsDatabaseHelper.DARK_THEME:
+                            // Turn on algorithmic darkening.
+                            WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
+                            break;
+                    }
+                } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
                     // Set the WebView theme.
                     switch (webViewThemeInt) {
                         case DomainsDatabaseHelper.SYSTEM_DEFAULT:
                             // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
                             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
                                 // Turn off the WebView dark mode.
+                                //noinspection deprecation
                                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
                             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
                                 // Turn on the WebView dark mode.
+                                //noinspection deprecation
                                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                             } else {  // The system default theme is selected.
                                 // Get the current system theme status.
@@ -4070,9 +4028,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 // Set the WebView theme according to the current system theme status.
                                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
                                     // Turn off the WebView dark mode.
+                                    //noinspection deprecation
                                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
                                 } else {  // The system is in night mode.
                                     // Turn on the WebView dark mode.
+                                    //noinspection deprecation
                                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                                 }
                             }
@@ -4080,11 +4040,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         case DomainsDatabaseHelper.LIGHT_THEME:
                             // Turn off the WebView dark mode.
+                            //noinspection deprecation
                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
                             break;
 
                         case DomainsDatabaseHelper.DARK_THEME:
                             // Turn on the WebView dark mode.
+                            //noinspection deprecation
                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                             break;
                     }
@@ -4120,28 +4082,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         break;
                 }
 
-                // Get the current theme status.
-                int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
-                } else {
-                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
-                }
+                urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null));
             } 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.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean(getString(R.string.javascript_key), 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));
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true));
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true));
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
-                nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
+                nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean(getString(R.string.dom_storage_key), false));
+                boolean saveFormData = sharedPreferences.getBoolean(getString(R.string.save_form_data_key), false);  // Form data can be removed once the minimum API >= 26.
+                nestedScrollWebView.setEasyListEnabled(sharedPreferences.getBoolean(getString(R.string.easylist_key), true));
+                nestedScrollWebView.setEasyPrivacyEnabled(sharedPreferences.getBoolean(getString(R.string.easyprivacy_key), true));
+                nestedScrollWebView.setFanboysAnnoyanceListEnabled(sharedPreferences.getBoolean(getString(R.string.fanboys_annoyance_list_key), true));
+                nestedScrollWebView.setFanboysSocialBlockingListEnabled(sharedPreferences.getBoolean(getString(R.string.fanboys_social_blocking_list_key), true));
+                nestedScrollWebView.setUltraListEnabled(sharedPreferences.getBoolean(getString(R.string.ultralist_key), true));
+                nestedScrollWebView.setUltraPrivacyEnabled(sharedPreferences.getBoolean(getString(R.string.ultraprivacy_key), true));
+                nestedScrollWebView.setBlockAllThirdPartyRequests(sharedPreferences.getBoolean(getString(R.string.block_all_third_party_requests_key), false));
 
                 // Apply the default cookie setting.
                 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
@@ -4192,7 +4147,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     case SETTINGS_CUSTOM_USER_AGENT:
                         // Set the default custom user agent.
-                        nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
+                        nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value)));
                         break;
 
                     default:
@@ -4201,13 +4156,30 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Apply the WebView theme if supported by the installed WebView.
-                if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
+                if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
+                    // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
+                    if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // the light theme is selected.
+                        // Turn off algorithmic darkening.
+                        WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
+                    } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
+                        // Turn on algorithmic darkening.
+                        WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
+                    } else {  // The system default theme is selected.
+                        // Get the current system theme status.
+                        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+                        // Set the algorithmic darkening according to the current system theme status.
+                        WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), currentThemeStatus == Configuration.UI_MODE_NIGHT_YES);
+                    }
+                } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
                     // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
                     if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
                         // Turn off the WebView dark mode.
+                        //noinspection deprecation
                         WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
                     } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
                         // Turn on the WebView dark mode.
+                        //noinspection deprecation
                         WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                     } else {  // The system default theme is selected.
                         // Get the current system theme status.
@@ -4216,9 +4188,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Set the WebView theme according to the current system theme status.
                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
                             // Turn off the WebView dark mode.
+                            //noinspection deprecation
                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
                         } else {  // The system is in night mode.
                             // Turn on the WebView dark mode.
+                            //noinspection deprecation
                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                         }
                     }
@@ -4248,13 +4222,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Load the URL if directed.  This makes sure that the domain settings are properly loaded before the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
         if (loadUrl) {
-            nestedScrollWebView.loadUrl(url, customHeaders);
+            nestedScrollWebView.loadUrl(url);
         }
     }
 
     private void applyProxy(boolean reloadWebViews) {
-        // Set the proxy according to the mode.  `this` refers to the current activity where an alert dialog might be displayed.
-        ProxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode);
+        // Set the proxy according to the mode.
+        proxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode);
 
         // Reset the waiting for proxy tracker.
         waitingForProxy = false;
@@ -4295,7 +4269,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     packageManager.getPackageInfo("org.torproject.android", 0);
 
                     // Check to see if the proxy is ready.
-                    if (!orbotStatus.equals("ON")) {  // Orbot is not ready.
+                    if (!orbotStatus.equals(ProxyHelper.ORBOT_STATUS_ON)) {  // Orbot is not ready.
                         // Set the waiting for proxy status.
                         waitingForProxy = true;
 
@@ -4304,8 +4278,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.
@@ -4314,8 +4294,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;
@@ -4327,22 +4313,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 } else {
                     appBarLayout.setBackgroundResource(R.color.dark_blue_30);
                 }
+                // Get the package manager.
+                PackageManager packageManager = getPackageManager();
 
                 // Check to see if I2P is installed.
                 try {
-                    // Get the package manager.
-                    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.
+                    // Check to see if the F-Droid flavor is installed.  This will throw an error and drop to the catch section if it isn't installed.
                     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);
+                } catch (PackageManager.NameNotFoundException fdroidException) {  // The F-Droid flavor is not installed.
+                    try {
+                        // Check to see if the Google Play flavor is installed.  This will throw an error and drop to the catch section if it isn't installed.
+                        packageManager.getPackageInfo("net.i2p.android", 0);
+                    } catch (PackageManager.NameNotFoundException googlePlayException) {  // The Google Play flavor 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;
@@ -4391,35 +4387,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode);
             }
 
-            // Get the current theme status.
-            int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
             // Update the cookies icon.
-            if (currentWebView.getAcceptCookies()) {  // Cookies are enabled.
+            if (currentWebView.getAcceptCookies()) {
                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
-            } else {  // Cookies are disabled.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
-                } else {
-                    optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
-                }
+            } else {
+                optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled);
             }
 
             // Update the refresh icon.
             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) {
-                    optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
-                } else {
-                    optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
-                }
+                // Set the icon.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
+                optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled);
             } else {  // The stop icon is displayed.
-                // Set the icon according to the theme.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
-                } else {
-                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
-                }
+                // Set the icon.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
+                optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
             }
 
             // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar.
@@ -4506,7 +4487,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
 
                 // Get the favorite icon byte array from the cursor.
-                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
+                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON));
 
                 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
                 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
@@ -4515,11 +4496,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
 
                 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
-                String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+                String bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
                 bookmarkNameTextView.setText(bookmarkNameString);
 
                 // Make the font bold for folders.
-                if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
+                if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
                     bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
                 } else {  // Reset the font to default for normal bookmarks.
                     bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
@@ -4585,49 +4566,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     private String sanitizeUrl(String url) {
-        // Sanitize Google Analytics.
-        if (sanitizeGoogleAnalytics) {
-            // Remove `?utm_`.
-            if (url.contains("?utm_")) {
-                url = url.substring(0, url.indexOf("?utm_"));
-            }
-
-            // Remove `&utm_`.
-            if (url.contains("&utm_")) {
-                url = url.substring(0, url.indexOf("&utm_"));
-            }
-        }
-
-        // Sanitize Facebook Click IDs.
-        if (sanitizeFacebookClickIds) {
-            // Remove `?fbclid=`.
-            if (url.contains("?fbclid=")) {
-                url = url.substring(0, url.indexOf("?fbclid="));
-            }
-
-            // Remove `&fbclid=`.
-            if (url.contains("&fbclid=")) {
-                url = url.substring(0, url.indexOf("&fbclid="));
-            }
-
-            // Remove `?fbadid=`.
-            if (url.contains("?fbadid=")) {
-                url = url.substring(0, url.indexOf("?fbadid="));
-            }
-
-            // Remove `&fbadid=`.
-            if (url.contains("&fbadid=")) {
-                url = url.substring(0, url.indexOf("&fbadid="));
-            }
-        }
+        // Sanitize tracking queries.
+        if (sanitizeTrackingQueries)
+            url = sanitizeUrlHelper.sanitizeTrackingQueries(url);
 
-        // Sanitize Twitter AMP redirects.
-        if (sanitizeTwitterAmpRedirects) {
-            // Remove `?amp=1`.
-            if (url.contains("?amp=1")) {
-                url = url.substring(0, url.indexOf("?amp=1"));
-            }
-        }
+        // Sanitize AMP redirects.
+        if (sanitizeAmpRedirects)
+            url = sanitizeUrlHelper.sanitizeAmpRedirects(url);
 
         // Return the sanitized URL.
         return url;
@@ -4723,7 +4668,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Add a new tab if specified in the preferences.
-                if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
+                if (sharedPreferences.getBoolean(getString(R.string.open_intents_in_new_tab_key), true)) {  // Load the URL in a new tab.
                     // Set the loading new intent flag.
                     loadingNewIntent = true;
 
@@ -4763,6 +4708,15 @@ 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.getTranslationY() != 0)) {
+            // Animate the bottom app bar onto the screen.
+            objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
+
+            // Make it so.
+            objectAnimator.start();
+        }
     }
 
     public void closeTab(View view) {
@@ -4805,8 +4759,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Enable the sliding drawers.
         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
 
-        // Show the main content relative layout.
-        mainContentRelativeLayout.setVisibility(View.VISIBLE);
+        // Show the coordinator layout.
+        coordinatorLayout.setVisibility(View.VISIBLE);
 
         // Apply the appropriate full screen mode flags.
         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
@@ -4819,15 +4773,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 actionBar.hide();
             }
 
-            // 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);
-
-                // Hide the banner ad.
-                AdHelper.hideAd(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.
@@ -4840,15 +4785,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // 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);
-
-            // Reload the ad.
-            AdHelper.loadAd(adView, this, this, getString(R.string.ad_unit_id));
-        }
     }
 
     private void clearAndExit() {
@@ -4860,7 +4796,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         bookmarksDatabaseHelper.close();
 
         // Get the status of the clear everything preference.
-        boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
+        boolean clearEverything = sharedPreferences.getBoolean(getString(R.string.clear_everything_key), true);
 
         // Get a handle for the runtime.
         Runtime runtime = Runtime.getRuntime();
@@ -4870,13 +4806,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         String privateDataDirectoryString = getApplicationInfo().dataDir;
 
         // Clear cookies.
-        if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
-            // The command to remove cookies changed slightly in API 21.
-            if (Build.VERSION.SDK_INT >= 21) {
-                CookieManager.getInstance().removeAllCookies(null);
-            } else {
-                CookieManager.getInstance().removeAllCookie();
-            }
+        if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cookies_key), true)) {
+            // Request the cookies be deleted.
+            CookieManager.getInstance().removeAllCookies(null);
 
             // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
             try {
@@ -4893,7 +4825,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Clear DOM storage.
-        if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
+        if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_dom_storage_key), true)) {
             // Ask `WebStorage` to clear the DOM storage.
             WebStorage webStorage = WebStorage.getInstance();
             webStorage.deleteAllData();
@@ -4921,7 +4853,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Clear form data if the API < 26.
-        if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
+        if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_form_data_key), true))) {
             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
             webViewDatabase.clearFormData();
 
@@ -4953,7 +4885,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Clear the cache.
-        if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
+        if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cache_key), true)) {
             // Clear the cache from each WebView.
             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
                 // Get the WebView tab fragment.
@@ -4979,7 +4911,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Delete the secondary `Service Worker` cache directory.
                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
-                Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
+                Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Default/Service Worker/"});
 
                 // Wait until the processes have finished.
                 deleteCacheProcess.waitFor();
@@ -5016,9 +4948,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         }
 
-        // Clear the custom headers.
-        customHeaders.clear();
-
         // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
         // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
         if (clearEverything) {
@@ -5034,11 +4963,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
-        if (Build.VERSION.SDK_INT >= 21) {
-            finishAndRemoveTask();
-        } else {
-            finish();
-        }
+        finishAndRemoveTask();
 
         // Remove the terminated program from RAM.  The status code is `0`.
         System.exit(0);
@@ -5130,16 +5055,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Set the background to indicate the domain settings status.
             if (currentWebView.getDomainSettingsApplied()) {
-                // Get the current theme status.
-                int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-                // Set a green background on the URL relative layout to indicate that custom domain settings are being used.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
-                } else {
-                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
-                }
+                // Set a background on the URL relative layout to indicate that custom domain settings are being used.
+                urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null));
             } else {
+                // Remove any background on the URL relative layout.
                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
             }
         } else {  // The fragment has not been populated.  Try again in 100 milliseconds.
@@ -5157,22 +5076,53 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
+    @SuppressLint("ClickableViewAccessibility")
     @Override
-    public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState) {
+    public void initializeWebView(@NonNull NestedScrollWebView nestedScrollWebView, int pageNumber, @NonNull ProgressBar progressBar, @NonNull String url, boolean restoringState) {
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
         // Get the WebView theme.
-        String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
+        String webViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value));
 
         // Get the WebView theme entry values string array.
         String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values);
 
         // Apply the WebView theme if supported by the installed WebView.
-        if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
+        if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
+            // Set the WebView them.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
+            if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
+                // Turn off algorithmic darkening.
+                WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
+
+                // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
+                // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
+                nestedScrollWebView.setVisibility(View.VISIBLE);
+            } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
+                // Turn on algorithmic darkening.
+                WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
+            } else {
+                // The system default theme is selected.
+                int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+                // Set the algorithmic darkening according to the current system theme status.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
+                    // Turn off algorithmic darkening.
+                    WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
+
+                    // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
+                    // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
+                    nestedScrollWebView.setVisibility(View.VISIBLE);
+                } else {  // The system is in night mode.
+                    // Turn on algorithmic darkening.
+                    WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
+                }
+            }
+        } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
             // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
                 // Turn off the WebView dark mode.
+                //noinspection deprecation
                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
 
                 // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
@@ -5180,6 +5130,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 nestedScrollWebView.setVisibility(View.VISIBLE);
             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
                 // Turn on the WebView dark mode.
+                //noinspection deprecation
                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
             } else {  // The system default theme is selected.
                 // Get the current system theme status.
@@ -5188,6 +5139,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Set the WebView theme according to the current system theme status.
                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
                     // Turn off the WebView dark mode.
+                    //noinspection deprecation
                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
 
                     // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
@@ -5195,6 +5147,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     nestedScrollWebView.setVisibility(View.VISIBLE);
                 } else {  // The system is in night mode.
                     // Turn on the WebView dark mode.
+                    //noinspection deprecation
                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
                 }
             }
@@ -5212,11 +5165,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Remove the lint warning below that the input method manager might be null.
         assert inputMethodManager != null;
 
-        // Initialize the favorite icon.
-        nestedScrollWebView.initializeFavoriteIcon();
-
         // Set the app bar scrolling.
-        nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
+        nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
 
         // Allow pinch to zoom.
         nestedScrollWebView.getSettings().setBuiltInZoomControls(true);
@@ -5225,9 +5175,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         nestedScrollWebView.getSettings().setDisplayZoomControls(false);
 
         // Don't allow mixed content (HTTP and HTTPS) on the same website.
-        if (Build.VERSION.SDK_INT >= 21) {
-            nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
-        }
+        nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
 
         // Set the WebView to load in overview mode (zoomed out to the maximum width).
         nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
@@ -5250,7 +5198,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Toggle the full screen browsing mode.
                     if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
                         // Hide the app bar if specified.
-                        if (hideAppBar) {
+                        if (hideAppBar) {  // The app bar is hidden.
                             // Close the find on page bar if it is visible.
                             closeFindOnPage(null);
 
@@ -5260,8 +5208,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Hide the action bar.
                             actionBar.hide();
 
-                            // Set layout and scrolling parameters if the app bar is at the top of the screen.
-                            if (!bottomAppBar) {
+                            // Set layout and scrolling parameters according to the position of the app bar.
+                            if (bottomAppBar) {  // The app bar is at the bottom.
+                                // Reset the WebView padding to fill the available space.
+                                swipeRefreshLayout.setPadding(0, 0, 0, 0);
+                            } else {  // The app bar is at the top.
                                 // 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.
@@ -5277,15 +5228,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                     swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
                                 }
                             }
-                        }
-
-                        // 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);
-
-                            // Hide the banner ad.
-                            AdHelper.hideAd(adView);
+                        } else {  // The app bar is not hidden.
+                            // Adjust the UI for the bottom app bar.
+                            if (bottomAppBar) {
+                                // Adjust the UI according to the scrolling of the app bar.
+                                if (scrollAppBar) {
+                                    // Reset the WebView padding to fill the available space.
+                                    swipeRefreshLayout.setPadding(0, 0, 0, 0);
+                                } else {
+                                    // Move the WebView above the app bar layout.
+                                    swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
+                                }
+                            }
                         }
 
                         /* Hide the system bars.
@@ -5304,33 +5258,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                             // Show the action bar.
                             actionBar.show();
-
-                            // 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")) {
-                            // 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));
+                        // Set layout and scrolling parameters according to the position of the app bar.
+                        if (bottomAppBar) {  // The app bar is at the bottom.
+                            // Adjust the UI.
+                            if (scrollAppBar) {
+                                // Reset the WebView padding to fill the available space.
+                                swipeRefreshLayout.setPadding(0, 0, 0, 0);
+                            } else {
+                                // Move the WebView above the app bar layout.
+                                swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
+                            }
+                        } else {  // The app bar is at the top.
+                            // 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);
+                            }
                         }
 
                         // Remove the `SYSTEM_UI` flags from the root frame layout.
@@ -5343,6 +5297,30 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     return false;
                 }
             }
+
+            @Override
+            public boolean onFling(MotionEvent motionEvent1, MotionEvent motionEvent2, float velocityX, float velocityY) {
+                // Scroll the bottom app bar if enabled.
+                if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) {
+                    // Calculate the Y change.
+                    float motionY = motionEvent2.getY() - motionEvent1.getY();
+
+                    // Scroll the app bar if the change is greater than 50 pixels.
+                    if (motionY > 50) {
+                        // Animate the bottom app bar onto the screen.
+                        objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
+                    } else if (motionY < -50) {
+                        // Animate the bottom app bar off the screen.
+                        objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight());
+                    }
+
+                    // Make it so.
+                    objectAnimator.start();
+                }
+
+                // Do not consume the event.
+                return false;
+            }
         });
 
         // Pass all touch events on the WebView through the double-tap gesture detector.
@@ -5378,23 +5356,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get the file name from the content disposition.
                 String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
 
-                // Prevent the dialog from displaying if the app window is not visible.
-                // The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
-                while (!activity.getWindow().isActive()) {
-                    try {
-                        // The window is not active.  Wait 1 second.
-                        wait(1000);
-                    } catch (InterruptedException e) {
-                        // Do nothing.
-                    }
-                }
-
                 // Instantiate the save dialog.
-                DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+                DialogFragment saveDialogFragment = SaveDialog.saveUrl(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)));
+                }
             }
         });
 
@@ -5421,68 +5394,30 @@ 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, 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);
-                } else {
-                    // Disable swipe to refresh.
-                    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) {
-                    /* 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 {
-            nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
-                if (nestedScrollWebView.getSwipeToRefresh()) {
-                    // Only enable swipe to refresh if the WebView is scrolled to the top.
-                    swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
-                } else {
-                    // Disable swipe to refresh.
-                    swipeRefreshLayout.setEnabled(false);
-                }
+        // Process scroll changes.
+        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);
+            } else {
+                // Disable swipe to refresh.
+                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) {
-                    /* 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);
-                }
-            });
-        }
+            // 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) {
+                /* 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);
+            }
+        });
 
         // Set the web chrome client.
         nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
@@ -5576,20 +5511,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // 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(adView);
-                }
-
                 // Hide the keyboard.
                 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
 
-                // Hide the main content relative layout.
-                mainContentRelativeLayout.setVisibility(View.GONE);
+                // Hide the coordinator layout.
+                coordinatorLayout.setVisibility(View.GONE);
 
                 /* Hide the system bars.
                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
@@ -5623,34 +5549,31 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Upload files.
             @Override
             public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
-                // Show the file chooser if the device is running API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Store the file path callback.
-                    fileChooserCallback = filePathCallback;
+                // Store the file path callback.
+                fileChooserCallback = filePathCallback;
 
-                    // Create an intent to open a chooser based on the file chooser parameters.
-                    Intent fileChooserIntent = fileChooserParams.createIntent();
+                // Create an intent to open a chooser based on the file chooser parameters.
+                Intent fileChooserIntent = fileChooserParams.createIntent();
 
-                    // Get a handle for the package manager.
-                    PackageManager packageManager = getPackageManager();
+                // Get a handle for the package manager.
+                PackageManager packageManager = getPackageManager();
 
-                    // Check to see if the file chooser intent resolves to an installed package.
-                    if (fileChooserIntent.resolveActivity(packageManager) != null) {  // The file chooser intent is fine.
-                        // Start the file chooser intent.
-                        startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
-                    } else {  // The file chooser intent will cause a crash.
-                        // Create a generic intent to open a chooser.
-                        Intent genericFileChooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
+                // Check to see if the file chooser intent resolves to an installed package.
+                if (fileChooserIntent.resolveActivity(packageManager) != null) {  // The file chooser intent is fine.
+                    // Start the file chooser intent.
+                    startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
+                } else {  // The file chooser intent will cause a crash.
+                    // Create a generic intent to open a chooser.
+                    Intent genericFileChooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
 
-                        // Request an openable file.
-                        genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE);
+                    // Request an openable file.
+                    genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE);
 
-                        // Set the file type to everything.
-                        genericFileChooserIntent.setType("*/*");
+                    // Set the file type to everything.
+                    genericFileChooserIntent.setType("*/*");
 
-                        // Start the generic file chooser intent.
-                        startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
-                    }
+                    // Start the generic file chooser intent.
+                    startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
                 }
                 return true;
             }
@@ -5740,7 +5663,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
             @Override
-            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+            public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest webResourceRequest) {
+                // Get the URL.
+                String url = webResourceRequest.getUrl().toString();
+
                 // Check to see if the resource request is for the main URL.
                 if (url.equals(nestedScrollWebView.getCurrentUrl())) {
                     // `return null` loads the resource request, which should never be blocked if it is the main URL.
@@ -5760,9 +5686,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     }
                 }
 
-                // Sanitize the URL.
-                url = sanitizeUrl(url);
-
                 // 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()));
 
@@ -5778,31 +5701,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Store a copy of the current domain for use in later requests.
                 String currentDomain = currentBaseDomain;
 
-                // Nobody is happy when comparing null strings.
-                if ((currentBaseDomain != null) && (url != null)) {
-                    // Convert the request URL to a URI.
-                    Uri requestUri = Uri.parse(url);
-
-                    // Get the request host name.
-                    String requestBaseDomain = requestUri.getHost();
-
-                    // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
-                    if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
-                        // Determine the current base domain.
-                        while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
-                            // Remove the first subdomain.
-                            currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
-                        }
+                // Get the request host name.
+                String requestBaseDomain = webResourceRequest.getUrl().getHost();
 
-                        // Determine the request base domain.
-                        while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
-                            // Remove the first subdomain.
-                            requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
-                        }
+                // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
+                if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
+                    // Determine the current base domain.
+                    while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
+                        // Remove the first subdomain.
+                        currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
+                    }
 
-                        // Update the third party request tracker.
-                        isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
+                    // Determine the request base domain.
+                    while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
+                        // Remove the first subdomain.
+                        requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
                     }
+
+                    // Update the third party request tracker.
+                    isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
                 }
 
                 // Get the current WebView page position.
@@ -5812,7 +5729,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
 
                 // Block third-party requests if enabled.
-                if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) {
+                if (isThirdPartyRequest && nestedScrollWebView.getBlockAllThirdPartyRequests()) {
                     // Add the result to the resource requests.
                     nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url});
 
@@ -5841,7 +5758,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Check UltraList if it is enabled.
-                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) {
+                if (nestedScrollWebView.getUltraListEnabled()) {
                     // Check the URL against UltraList.
                     String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
 
@@ -5881,7 +5798,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Check UltraPrivacy if it is enabled.
-                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) {
+                if (nestedScrollWebView.getUltraPrivacyEnabled()) {
                     // Check the URL against UltraPrivacy.
                     String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
 
@@ -5923,7 +5840,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Check EasyList if it is enabled.
-                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) {
+                if (nestedScrollWebView.getEasyListEnabled()) {
                     // Check the URL against EasyList.
                     String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
 
@@ -5960,7 +5877,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Check EasyPrivacy if it is enabled.
-                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) {
+                if (nestedScrollWebView.getEasyPrivacyEnabled()) {
                     // Check the URL against EasyPrivacy.
                     String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
 
@@ -5998,7 +5915,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Check Fanboy’s Annoyance List if it is enabled.
-                if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) {
+                if (nestedScrollWebView.getFanboysAnnoyanceListEnabled()) {
                     // Check the URL against Fanboy's Annoyance List.
                     String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
 
@@ -6035,7 +5952,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
                                 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
                     }
-                } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
+                } else if (nestedScrollWebView.getFanboysSocialBlockingListEnabled()) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
                     // Check the URL against Fanboy's Annoyance List.
                     String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
 
@@ -6100,8 +6017,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             @Override
             public void onPageStarted(WebView view, String url, Bitmap favicon) {
-                // Set the padding and layout settings if the app bar is at the top of the screen.
-                if (!bottomAppBar) {
+                // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
+                // This should only be populated if it is greater than 0 because otherwise it will be reset to 0 if the app bar is hidden in full screen browsing mode.
+                if (appBarLayout.getHeight() > 0) appBarHeight = appBarLayout.getHeight();
+
+                // Set the padding and layout settings according to the position of the app bar.
+                if (bottomAppBar) {  // The app bar is on the bottom.
+                    // Adjust the UI.
+                    if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
+                        // Reset the WebView padding to fill the available space.
+                        swipeRefreshLayout.setPadding(0, 0, 0, 0);
+                    } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
+                        // Move the WebView above the app bar layout.
+                        swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
+                    }
+                } else {  // The app bar is on the top.
                     // 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.
@@ -6110,9 +6040,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // 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);
 
@@ -6135,7 +6062,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.
@@ -6143,7 +6070,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Reset the list of host IP addresses.
-                nestedScrollWebView.clearCurrentIpAddresses();
+                nestedScrollWebView.setCurrentIpAddresses("");
 
                 // Get a URI for the current URL.
                 Uri currentUri = Uri.parse(url);
@@ -6156,28 +6083,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Set the title.
                     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);
-
-                    // If the icon is displayed in the AppBar, set it according to the theme.
-                    if (displayAdditionalAppBarIcons) {
-                        // Get the current theme status.
-                        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-                        // Set the stop icon according to the theme.
-                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                            optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
-                        } else {
-                            optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
-                        }
-                    }
+                    // Set the icon if it is displayed in the AppBar.
+                    if (displayAdditionalAppBarIcons)
+                        optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
                 }
             }
 
             @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.getAcceptCookies() && Build.VERSION.SDK_INT >= 21) {
+                if (nestedScrollWebView.getAcceptCookies()) {
                     CookieManager.getInstance().flush();
                 }
 
@@ -6186,23 +6101,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Reset the Refresh title.
                     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);
-
-                    // If the icon is displayed in the app bar, reset it according to the theme.
-                    if (displayAdditionalAppBarIcons) {
-                        // Get the current theme status.
-                        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-                        // Set the icon according to the theme.
-                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                            optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
-                        } else {
-                            optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
-                        }
-                    }
+                    // Reset the icon if it is displayed in the app bar.
+                    if (displayAdditionalAppBarIcons)
+                        optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled);
                 }
 
+                 // 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;
+
                 // Clear the cache, history, and logcat if Incognito Mode is enabled.
                 if (incognitoModeEnabled) {
                     // Clear the cache.  `true` includes disk files.
@@ -6213,16 +6120,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     // Manually delete cache folders.
                     try {
-                        // 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;
-
                         // Delete the main cache directory.
                         Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache");
-
-                        // Delete the secondary `Service Worker` cache directory.
-                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
-                        Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
                     } catch (IOException exception) {
                         // Do nothing if an error is thrown.
                     }
@@ -6236,6 +6135,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     }
                 }
 
+                // Clear the `Service Worker` directory.
+                try {
+                    // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+                    Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Default/Service Worker/"});
+                } catch (IOException exception) {
+                    // Do nothing.
+                }
+
                 // Get the current page position.
                 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
 
@@ -6308,7 +6215,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
             }
 
-            // Handle SSL Certificate errors.
+            // Handle SSL Certificate errors.  Suppress the lint warning that ignoring the error might be dangerous.
+            @SuppressLint("WebViewClientOnReceivedSslError")
             @Override
             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                 // Get the current website SSL certificate.
@@ -6327,11 +6235,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
                 if (nestedScrollWebView.hasPinnedSslCertificate()) {
                     // Get the pinned SSL certificate.
-                    ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
+                    Pair<String[], Date[]> pinnedSslCertificatePair = nestedScrollWebView.getPinnedSslCertificate();
 
                     // Extract the arrays from the array list.
-                    String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
-                    Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
+                    String[] pinnedSslCertificateStringArray = pinnedSslCertificatePair.getFirst();
+                    Date[] pinnedSslCertificateDateArray = pinnedSslCertificatePair.getSecond();
 
                     // Check if the current SSL certificate matches the pinned certificate.
                     if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&
@@ -6346,22 +6254,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Store the SSL error handler.
                     nestedScrollWebView.setSslErrorHandler(handler);
 
-                    // Prevent the dialog from displaying if the app window is not visible.
-                    // 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.
-                    while (!activity.getWindow().isActive()) {
-                        try {
-                            // The window is not active.  Wait 1 second.
-                            wait(1000);
-                        } catch (InterruptedException e) {
-                            // Do nothing.
-                        }
-                    }
-
                     // 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)));
+                    }
                 }
             }
         });
@@ -6449,4 +6352,4 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         }
     }
-}
\ No newline at end of file
+}