]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Don't replace a high resolution favorite icon with a lower resolution one. https...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index d592b993d7c5fc8f097ee25e0758b0b04d753a04..0f412f01d888cd3a638369907e07f1a1c57d21ac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2015-2022 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>.
  *
@@ -46,13 +46,11 @@ import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
 import android.net.http.SslError;
-import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
-import android.preference.PreferenceManager;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintManager;
 import android.provider.DocumentsContract;
@@ -99,6 +97,8 @@ import android.widget.RadioButton;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.activity.OnBackPressedCallback;
+import androidx.activity.result.ActivityResult;
 import androidx.activity.result.ActivityResultCallback;
 import androidx.activity.result.ActivityResultLauncher;
 import androidx.activity.result.contract.ActivityResultContracts;
@@ -114,6 +114,7 @@ import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceManager;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import androidx.viewpager.widget.ViewPager;
 import androidx.webkit.WebSettingsCompat;
@@ -127,16 +128,15 @@ import com.google.android.material.tabs.TabLayout;
 
 import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
-import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
-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.dataclasses.PendingDialog;
+import com.stoutner.privacybrowser.coroutines.GetHostIpAddressesCoroutine;
+import com.stoutner.privacybrowser.coroutines.PopulateBlocklistsCoroutine;
+import com.stoutner.privacybrowser.coroutines.PrepareSaveDialogCoroutine;
+import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
-import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
 import com.stoutner.privacybrowser.dialogs.OpenDialog;
@@ -152,6 +152,7 @@ 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;
@@ -173,28 +174,27 @@ import java.text.NumberFormat;
 
 import java.util.ArrayList;
 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, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener,
-        WebViewTabFragment.NewTabListener {
+        FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener,
+        PopulateBlocklistsCoroutine.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
 
     // Define the public static variables.
-    public static ExecutorService executorService = Executors.newFixedThreadPool(4);
+    public static final ExecutorService executorService = Executors.newFixedThreadPool(4);
     public static String orbotStatus = "unknown";
-    public static ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
+    public static final ArrayList<PendingDialogDataClass> pendingDialogsArrayList =  new ArrayList<>();
     public static String proxyMode = ProxyHelper.NONE;
 
     // Declare the public static variables.
-    public static String currentBookmarksFolder;
+    public static String currentBookmarksFolder = "";
     public static boolean restartFromBookmarksActivity;
     public static WebViewPagerAdapter webViewPagerAdapter;
 
@@ -209,15 +209,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
     public final static int DOMAINS_CUSTOM_USER_AGENT = 12;
 
-    // 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;
-
     // Define the saved instance state constants.
+    private final String BOOKMARKS_DRAWER_PINNED = "bookmarks_drawer_pinned";
+    private final String PROXY_MODE = "proxy_mode";
     private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
     private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
     private final String SAVED_TAB_POSITION = "saved_tab_position";
-    private final String PROXY_MODE = "proxy_mode";
 
     // Define the saved instance state variables.
     private ArrayList<Bundle> savedStateArrayList;
@@ -225,17 +222,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private int savedTabPosition;
     private String savedProxyMode;
 
-    // Define the class variables.
-    @SuppressWarnings("rawtypes")
-    AsyncTask populateBlocklists;
-
     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
     // `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;
 
@@ -261,9 +251,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     private CursorAdapter bookmarksCursorAdapter;
 
-    // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
-    private String oldFolderNameString;
-
     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
     private ValueCallback<Uri[]> fileChooserCallback;
 
@@ -272,14 +259,16 @@ 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 BookmarksDatabaseHelper bookmarksDatabaseHelper;
+    private boolean bookmarksDrawerPinned;
     private boolean bottomAppBar;
+    private boolean displayAdditionalAppBarIcons;
     private boolean displayingFullScreenVideo;
     private boolean downloadWithExternalApp;
     private boolean fullScreenBrowsingModeEnabled;
@@ -288,9 +277,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private boolean inFullScreenBrowsingMode;
     private boolean loadingNewIntent;
     private BroadcastReceiver orbotStatusBroadcastReceiver;
-    private ProxyHelper proxyHelper;
     private boolean reapplyAppSettingsOnRestart;
     private boolean reapplyDomainSettingsOnRestart;
+    private boolean sanitizeAmpRedirects;
+    private boolean sanitizeTrackingQueries;
     private boolean scrollAppBar;
     private boolean waitingForProxy;
     private String webViewDefaultUserAgent;
@@ -300,19 +290,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private String saveUrlString = "";
 
     // Declare the class views.
-    private FrameLayout rootFrameLayout;
-    private DrawerLayout drawerLayout;
-    private CoordinatorLayout coordinatorLayout;
-    private Toolbar toolbar;
-    private RelativeLayout urlRelativeLayout;
-    private EditText urlEditText;
     private ActionBar actionBar;
+    private CoordinatorLayout coordinatorLayout;
+    private ImageView bookmarksDrawerPinnedImageView;
+    private DrawerLayout drawerLayout;
     private LinearLayout findOnPageLinearLayout;
+    private FrameLayout fullScreenVideoFrameLayout;
+    private FrameLayout rootFrameLayout;
+    private SwipeRefreshLayout swipeRefreshLayout;
     private LinearLayout tabsLinearLayout;
     private TabLayout tabLayout;
-    private SwipeRefreshLayout swipeRefreshLayout;
+    private Toolbar toolbar;
+    private EditText urlEditText;
+    private RelativeLayout urlRelativeLayout;
     private ViewPager webViewPager;
-    private FrameLayout fullScreenVideoFrameLayout;
 
     // Declare the class menus.
     private Menu optionsMenu;
@@ -369,7 +360,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     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(),
+    private final ActivityResultLauncher<String> saveUrlActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("*/*"),
             new ActivityResultCallback<Uri>() {
                 @Override
                 public void onActivityResult(Uri fileUri) {
@@ -384,17 +375,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             });
 
     // 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(),
+    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());
-
-                            // Save the temporary MHT file.
                             currentWebView.saveWebArchive(temporaryMhtFile.toString(), false, callbackValue -> {
                                 if (callbackValue != null) {  // The temporary MHT file was saved successfully.
                                     try {
@@ -419,29 +429,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                         mhtOutputStream.close();
                                         temporaryMhtFileInputStream.close();
 
-                                        // Initialize the file name string from the file URI last path segment.
-                                        String fileNameString = 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.
-                                            fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
-
-                                            // Close the cursor.
-                                            contentResolverCursor.close();
-                                        }
-
                                         // Display a snackbar.
-                                        Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + fileNameString, Snackbar.LENGTH_SHORT).show();
+                                        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) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
+                                        Snackbar.make(currentWebView, getString(R.string.error_saving_file, finalFileNameString, exception), Snackbar.LENGTH_INDEFINITE).show();
                                     } finally {
                                         // Delete the temporary MHT file.
                                         //noinspection ResultOfMethodCallIgnored
@@ -449,19 +441,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                     }
                                 } 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();
+                                    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) + "  " + ioException, Snackbar.LENGTH_INDEFINITE).show();
+                            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(),
+    private final ActivityResultLauncher<String> saveWebpageImageActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("image/png"),
             new ActivityResultCallback<Uri>() {
                 @Override
                 public void onActivityResult(Uri fileUri) {
@@ -473,6 +465,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
             });
 
+    // Define the save webpage image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
+    private final ActivityResultLauncher<Intent> browseFileUploadActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
+            new ActivityResultCallback<ActivityResult>() {
+                @Override
+                public void onActivityResult(ActivityResult activityResult) {
+                    // Pass the file to the WebView.
+                    fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(activityResult.getResultCode(), activityResult.getData()));
+                }
+            });
+
     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with WebView.
     @SuppressLint("ClickableViewAccessibility")
     @Override
@@ -486,6 +488,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Check to see if the activity has been restarted.
         if (savedInstanceState != null) {
             // Store the saved instance state variables.
+            bookmarksDrawerPinned = savedInstanceState.getBoolean(BOOKMARKS_DRAWER_PINNED);
             savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
             savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
             savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
@@ -499,9 +502,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);
@@ -531,15 +535,9 @@ 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.
         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);
@@ -553,6 +551,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
         webViewPager = findViewById(R.id.webviewpager);
         NavigationView navigationView = findViewById(R.id.navigationview);
+        bookmarksDrawerPinnedImageView = findViewById(R.id.bookmarks_drawer_pinned_imageview);
         fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
 
         // Get a handle for the navigation menu.
@@ -593,6 +592,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());
 
@@ -602,8 +604,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Store up to 100 tabs in memory.
         webViewPager.setOffscreenPageLimit(100);
 
-        // Instantiate the proxy helper.
+        // Instantiate the helpers.
+        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
+        domainsDatabaseHelper = new DomainsDatabaseHelper(this);
         proxyHelper = new ProxyHelper();
+        sanitizeUrlHelper = new SanitizeUrlHelper();
+
+        // Update the bookmarks drawer pinned image view.
+        updateBookmarksDrawerPinnedImageView();
 
         // Initialize the app.
         initializeApp();
@@ -611,8 +619,51 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // 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();
+                    // It shouldn't be possible for the currentWebView to be null, but crash logs indicate it sometimes happens.
+                } else if ((currentWebView != null) && (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.
+                    // Run clear and exit.
+                    clearAndExit();
+                }
+            }
+        };
+
+        // Register the on back pressed callback.
+        getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
+
+        // Instantiate the populate blocklists coroutine.
+        PopulateBlocklistsCoroutine populateBlocklistsCoroutine = new PopulateBlocklistsCoroutine(this);
+
         // Populate the blocklists.
-        populateBlocklists = new PopulateBlocklists(this, this).execute();
+        populateBlocklistsCoroutine.populateBlocklists(this);
     }
 
     @Override
@@ -620,9 +671,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.
@@ -673,7 +721,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;
 
@@ -694,6 +742,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);
         }
     }
 
@@ -805,10 +856,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // 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);
+            PendingDialogDataClass pendingDialogDataClass = pendingDialogsArrayList.get(i);
 
             // Show the pending dialog.
-            pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
+            pendingDialogDataClass.dialogFragment.show(getSupportFragmentManager(), pendingDialogDataClass.tag);
         }
 
         // Clear the pending dialogs array list.
@@ -882,10 +933,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         int currentTabPosition = tabLayout.getSelectedTabPosition();
 
         // Store the saved states in the bundle.
+        savedInstanceState.putBoolean(BOOKMARKS_DRAWER_PINNED, bookmarksDrawerPinned);
+        savedInstanceState.putString(PROXY_MODE, proxyMode);
         savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
         savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
         savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
-        savedInstanceState.putString(PROXY_MODE, proxyMode);
     }
 
     @Override
@@ -905,11 +957,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             bookmarksDatabaseHelper.close();
         }
 
-        // Stop populating the blocklists if the AsyncTask is running in the background.
-        if (populateBlocklists != null) {
-            populateBlocklists.cancel(true);
-        }
-
         // Run the default commands.
         super.onDestroy();
     }
@@ -977,11 +1024,8 @@ 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);
 
-        // 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);
+        // Only display the dark WebView menu item if the API >= 29.
+        optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 29);
 
         // 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) {
@@ -1060,10 +1104,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Enable DOM Storage if JavaScript is enabled.
             optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
 
-            // Set the checkbox status for dark WebView if the WebView supports it.
-            if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
-                optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
-            }
+            // Get the current theme status.
+            int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+            // Enable dark WebView if night mode is enabled.
+            optionsDarkWebViewMenuItem.setEnabled(currentThemeStatus == Configuration.UI_MODE_NIGHT_YES);
+
+            // Set the checkbox status for dark WebView if the device is running API >= 29 and algorithmic darkening is supported.
+            if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING))
+                optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings()));
         }
 
         // Set the cookies menu item checked status.
@@ -1687,7 +1736,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();
@@ -1740,17 +1789,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Consume the event.
             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)) {
-                // Toggle the dark WebView setting.
-                if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) {  // Dark WebView is currently enabled.
-                    // Turn off dark WebView.
-                    WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
-                } else {  // Dark WebView is currently disabled.
-                    // Turn on dark WebView.
-                    WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
-                }
-            }
+            // Toggle dark WebView if supported.
+            if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING))
+                WebSettingsCompat.setAlgorithmicDarkeningAllowed(currentWebView.getSettings(), !WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings()));
 
             // Consume the event.
             return true;
@@ -1808,8 +1849,8 @@ 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(), currentWebView.getSettings().getUserAgentString(),
-                        currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
+                PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptCookies());
             }
 
             // Consume the event.
@@ -1829,7 +1870,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         } else if (menuItemId == R.id.add_to_homescreen) {  // Add to homescreen.
             // Instantiate the create home screen shortcut dialog.
             DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
-                    currentWebView.getFavoriteOrDefaultIcon());
+                    currentWebView.getFavoriteIcon());
 
             // Show the create home screen shortcut dialog.
             createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
@@ -1849,24 +1890,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;
@@ -1883,19 +1945,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("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                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();
@@ -1913,28 +1975,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);
+                    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);
@@ -1943,10 +2005,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("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+                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();
@@ -1964,14 +2026,14 @@ 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);
+                    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.
@@ -2109,8 +2171,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("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+            domainsIntent.putExtra(DomainsActivity.CURRENT_URL, currentWebView.getUrl());
+            domainsIntent.putExtra(DomainsActivity.CURRENT_IP_ADDRESSES, currentWebView.getCurrentIpAddresses());
 
             // Get the current certificate.
             SslCertificate sslCertificate = currentWebView.getCertificate();
@@ -2162,6 +2224,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);
@@ -2275,8 +2346,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(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2347,8 +2417,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(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2452,8 +2521,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(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2479,8 +2547,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(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2641,188 +2708,13 @@ 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.
         bookmarksListView.setSelection(0);
     }
 
-    @Override
-    public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
-        // Remove the incorrect lint warning below that the dialog fragment might be null.
-        assert dialogFragment != null;
-
-        // Get the dialog.
-        Dialog dialog = dialogFragment.getDialog();
-
-        // Remove the incorrect lint warning below that the dialog might be null.
-        assert dialog != null;
-
-        // Get handles for the views from the dialog.
-        RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
-        RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
-        ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
-        EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
-
-        // Get the new folder name.
-        String newFolderNameString = editFolderNameEditText.getText().toString();
-
-        // Check if the favorite icon has changed.
-        if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
-            // Update the name in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
-        } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
-            // Create the new folder icon Bitmap.
-            Bitmap folderIconBitmap;
-
-            // Populate the new folder icon bitmap.
-            if (defaultFolderIconRadioButton.isChecked()) {
-                // Get the default folder icon drawable.
-                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
-
-                // Convert the folder icon drawable to a bitmap drawable.
-                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
-
-                // Convert the folder icon bitmap drawable to a bitmap.
-                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-            } else {  // Use the `WebView` favorite icon.
-                // Copy the favorite icon bitmap to the folder icon bitmap.
-                folderIconBitmap = favoriteIconBitmap;
-            }
-
-            // Create a folder icon byte array output stream.
-            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
-
-            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
-
-            // Convert the folder icon byte array stream to a byte array.
-            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
-
-            // Update the folder icon in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
-        } else {  // The folder icon and the name have changed.
-            // Get the new folder icon bitmap.
-            Bitmap folderIconBitmap;
-            if (defaultFolderIconRadioButton.isChecked()) {
-                // Get the default folder icon drawable.
-                Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
-
-                // Convert the folder icon drawable to a bitmap drawable.
-                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
-
-                // Convert the folder icon bitmap drawable to a bitmap.
-                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
-            } else {  // Use the `WebView` favorite icon.
-                // Copy the favorite icon bitmap to the folder icon bitmap.
-                folderIconBitmap = favoriteIconBitmap;
-            }
-
-            // Create a folder icon byte array output stream.
-            ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
-
-            // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
-            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
-
-            // Convert the folder icon byte array stream to a byte array.
-            byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
-
-            // Update the folder name and icon in the database.
-            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
-        }
-
-        // Update the bookmarks cursor with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
-
-        // Update the `ListView`.
-        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.
-            finishAndRemoveTask();
-
-            // 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) {
-        // Run the default commands.
-        super.onActivityResult(requestCode, resultCode, returnedIntent);
-
-        // Run the commands that correlate to the specified request code.
-        switch (requestCode) {
-            case BROWSE_FILE_UPLOAD_REQUEST_CODE:
-                // Pass the file to the WebView.
-                fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
-                break;
-
-            case BROWSE_OPEN_REQUEST_CODE:
-                // Don't do anything if the user pressed back from the file picker.
-                if (resultCode == Activity.RESULT_OK) {
-                    // Get a handle for the open dialog fragment.
-                    DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
-
-                    // Only update the file name if the dialog still exists.
-                    if (openDialogFragment != null) {
-                        // Get a handle for the open dialog.
-                        Dialog openDialog = openDialogFragment.getDialog();
-
-                        // Remove the incorrect lint warning below that the dialog might be null.
-                        assert openDialog != null;
-
-                        // Get a handle for the file name edit text.
-                        EditText fileNameEditText = openDialog.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;
-        }
-    }
-
     private void loadUrlFromTextBox() {
         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
         String unformattedUrlString = urlEditText.getText().toString().trim();
@@ -3072,19 +2964,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.
+        // Initialize the color spans for highlighting the URLs.
         initialGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500));
         finalGrayColorSpan = new ForegroundColorSpan(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(getColor(R.color.red_a700));
-        } else {
-            redColorSpan = new ForegroundColorSpan(getColor(R.color.red_900));
-        }
+        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) -> {
@@ -3241,7 +3124,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(), currentWebView.getFavoriteOrDefaultIcon());
+                DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId(), currentWebView.getFavoriteIcon());
 
                 // Display the View SSL Certificate dialog.
                 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
@@ -3257,7 +3140,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
         launchBookmarksActivityFab.setOnClickListener(v -> {
             // Get a copy of the favorite icon bitmap.
-            Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
+            Bitmap favoriteIconBitmap = currentWebView.getFavoriteIcon();
 
             // Create a favorite icon byte array output stream.
             ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
@@ -3284,7 +3167,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the create new bookmark folder FAB to display an alert dialog.
         createBookmarkFolderFab.setOnClickListener(v -> {
             // Create a create bookmark folder dialog.
-            DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
+            DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteIcon());
 
             // Show the create bookmark folder dialog.
             createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
@@ -3293,7 +3176,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the create new bookmark FAB to display an alert dialog.
         createBookmarkFab.setOnClickListener(view -> {
             // Instantiate the create bookmark dialog.
-            DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
+            DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteIcon());
 
             // Display the create bookmark dialog.
             createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
@@ -3363,15 +3246,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;
@@ -3393,14 +3271,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Load the bookmark URL.
                 loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)));
 
-                // Close the bookmarks drawer.
-                drawerLayout.closeDrawer(GravityCompat.END);
+                // Close the bookmarks drawer if it is not pinned.
+                if (!bookmarksDrawerPinned)
+                    drawerLayout.closeDrawer(GravityCompat.END);
             }
 
-            // Close the `Cursor`.
+            // Close the cursor.
             bookmarkCursor.close();
         });
 
+        // Handle long-presses on bookmarks.
         bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
             // Convert the database ID from `long` to `int`.
             int databaseId = (int) id;
@@ -3410,14 +3290,23 @@ 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.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
+                // Get a cursor of all the bookmarks in the folder.
+                Cursor bookmarksCursor = bookmarksDatabaseHelper.getFolderBookmarks(databaseId);
 
-                // Instantiate the edit folder bookmark dialog.
-                DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
+                // Move to the first entry in the cursor.
+                bookmarksCursor.moveToFirst();
 
-                // Show the edit folder bookmark dialog.
-                editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
+                // Open each bookmark
+                for (int i = 0; i < bookmarksCursor.getCount(); i++) {
+                    // Load the bookmark in a new tab, moving to the tab for the first bookmark if the drawer is not pinned.
+                    addNewTab(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), (!bookmarksDrawerPinned && (i == 0)));
+
+                    // Move to the next bookmark.
+                    bookmarksCursor.moveToNext();
+                }
+
+                // Close the cursor.
+                bookmarksCursor.close();
             } else {  // The bookmark is not a folder.
                 // Get the bookmark cursor for this ID.
                 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
@@ -3425,13 +3314,17 @@ 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.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
+                // Load the bookmark in a new tab and move to the tab if the drawer is not pinned.
+                addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), !bookmarksDrawerPinned);
 
-                // Display a snackbar.
-                Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
+                // Close the cursor.
+                bookmarkCursor.close();
             }
 
+            // Close the bookmarks drawer if it is not pinned.
+            if (!bookmarksDrawerPinned)
+                drawerLayout.closeDrawer(GravityCompat.END);
+
             // Consume the event.
             return true;
         });
@@ -3478,9 +3371,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
-        // 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);
 
@@ -3499,14 +3389,13 @@ 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(getString(R.string.scroll_app_bar_key), true);
 
         // Apply the saved proxy mode if the app has been restarted.
@@ -3519,20 +3408,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();
@@ -3693,17 +3599,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
 
                     // Set the default favorite icon as the favorite icon for this tab.
-                    tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
+                    tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteIcon(), 64, 64, true));
 
                     // Set the loading title text.
                     tabTitleTextView.setText(R.string.loading);
                 }
             }
 
-            // 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`.
@@ -3712,7 +3615,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Get the domain name column index.
             int domainNameColumnIndex = domainNameCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME);
 
-            // Populate `domainSettingsSet`.
+            // Populate the domain settings set.
             for (int i = 0; i < domainNameCursor.getCount(); i++) {
                 // Move the domains cursor to the current row.
                 domainNameCursor.moveToPosition(i);
@@ -3721,7 +3624,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
             }
 
-            // Close `domainNameCursor.
+            // Close the domain name cursor.
             domainNameCursor.close();
 
             // Initialize the domain name in database variable.
@@ -3740,7 +3643,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);
@@ -3758,12 +3661,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);
@@ -3776,12 +3679,17 @@ 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.getColumnIndexOrThrow(DomainsDatabaseHelper._ID)));
+                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);
@@ -3789,12 +3697,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 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.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);
+                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));
@@ -3867,7 +3777,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:
@@ -3902,8 +3812,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                         // Update the swipe refresh layout.
                         if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
-                            // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
-                            swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
+                            // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null).
+                            if (currentWebView != null) {
+                                // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
+                                swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
+                            }
                         } else {  // Swipe to refresh is disabled.
                             // Disable the swipe refresh layout.
                             swipeRefreshLayout.setEnabled(false);
@@ -3914,8 +3827,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Store the swipe to refresh status in the nested scroll WebView.
                         nestedScrollWebView.setSwipeToRefresh(true);
 
-                        // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
-                        swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
+
+                        // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null).
+                        if (currentWebView != null) {
+                            // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
+                            swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
+                        }
                         break;
 
                     case DomainsDatabaseHelper.DISABLED:
@@ -3924,43 +3841,38 @@ 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)) {
+                // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported.
+                if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
                     // 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.
-                                WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+                                // Turn off algorithmic darkening.
+                                WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
                             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
-                                // Turn on the WebView dark mode.
-                                WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+                                // 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 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.
-                                    WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
-                                } else {  // The system is in night mode.
-                                    // Turn on the WebView dark mode.
-                                    WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
-                                }
+                                // 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 the WebView dark mode.
-                            WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+                            // Turn off algorithmic darkening.
+                            WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
                             break;
 
                         case DomainsDatabaseHelper.DARK_THEME:
-                            // Turn on the WebView dark mode.
-                            WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+                            // Turn on algorithmic darkening.
+                            WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
                             break;
                     }
                 }
@@ -3999,17 +3911,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 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.setEasyListEnabled(sharedPreferences.getBoolean("easylist", true));
-                nestedScrollWebView.setEasyPrivacyEnabled(sharedPreferences.getBoolean("easyprivacy", true));
-                nestedScrollWebView.setFanboysAnnoyanceListEnabled(sharedPreferences.getBoolean("fanboys_annoyance_list", true));
-                nestedScrollWebView.setFanboysSocialBlockingListEnabled(sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
-                nestedScrollWebView.setUltraListEnabled(sharedPreferences.getBoolean("ultralist", true));
-                nestedScrollWebView.setUltraPrivacyEnabled(sharedPreferences.getBoolean("ultraprivacy", true));
-                nestedScrollWebView.setBlockAllThirdPartyRequests(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());
@@ -4033,8 +3945,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Update the swipe refresh layout.
                 if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
-                    // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
-                    swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
+                    // Update the status of the swipe refresh layout if the current WebView is not null (crash reports indicate that in some unexpected way it sometimes is null).
+                    if (currentWebView != null) {
+                        // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
+                        swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
+                    }
                 } else {  // Swipe to refresh is disabled.
                     // Disable the swipe refresh layout.
                     swipeRefreshLayout.setEnabled(false);
@@ -4060,7 +3975,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:
@@ -4068,27 +3983,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
                 }
 
-                // Apply the WebView theme if supported by the installed WebView.
-                if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
+                // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported.
+                if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
                     // 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.
-                        WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+                    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 the WebView dark mode.
-                        WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+                        // 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 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.
-                            WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
-                        } else {  // The system is in night mode.
-                            // Turn on the WebView dark mode.
-                            WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
-                        }
+                        // Set the algorithmic darkening according to the current system theme status.
+                        WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), currentThemeStatus == Configuration.UI_MODE_NIGHT_YES);
                     }
                 }
 
@@ -4116,7 +4025,7 @@ 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);
         }
     }
 
@@ -4178,7 +4087,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 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)));
+                                pendingDialogsArrayList.add(new PendingDialogDataClass(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
                             }
                         }
                     }
@@ -4194,7 +4103,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             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)));
+                            pendingDialogsArrayList.add(new PendingDialogDataClass(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
                         }
                     }
                 }
@@ -4207,27 +4116,31 @@ 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);
 
-                        // 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)));
+                            // 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 PendingDialogDataClass(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                            }
                         }
                     }
                 }
@@ -4456,49 +4369,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;
@@ -4594,7 +4471,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;
 
@@ -4722,7 +4599,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();
@@ -4732,7 +4609,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         String privateDataDirectoryString = getApplicationInfo().dataDir;
 
         // Clear cookies.
-        if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
+        if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_cookies_key), true)) {
             // Request the cookies be deleted.
             CookieManager.getInstance().removeAllCookies(null);
 
@@ -4751,7 +4628,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();
@@ -4779,7 +4656,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();
 
@@ -4811,7 +4688,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.
@@ -4837,7 +4714,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();
@@ -4874,9 +4751,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) {
@@ -4911,6 +4785,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
+    public void toggleBookmarksDrawerPinned(View view) {
+        // Toggle the bookmarks drawer pinned tracker.
+        bookmarksDrawerPinned = !bookmarksDrawerPinned;
+
+        // Update the bookmarks drawer pinned image view.
+        updateBookmarksDrawerPinnedImageView();
+    }
+
+    private void updateBookmarksDrawerPinnedImageView() {
+        // Set the current icon.
+        if (bookmarksDrawerPinned)
+            bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin_selected);
+        else
+            bookmarksDrawerPinnedImageView.setImageResource(R.drawable.pin);
+    }
+
     private void setCurrentWebView(int pageNumber) {
         // Stop the swipe to refresh indicator if it is running
         swipeRefreshLayout.setRefreshing(false);
@@ -5007,44 +4897,44 @@ 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)) {
-            // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
+        // Set the WebView theme if device is running API >= 29 and algorithmic darkening is supported.
+        if ((Build.VERSION.SDK_INT >= 29) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
+            // 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 the WebView dark mode.
-                WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+                // 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 the WebView dark mode.
-                WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
-            } else {  // The system default theme is selected.
-                // Get the current system theme status.
+                // 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 WebView theme according to the current system theme status.
+                // 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 the WebView dark mode.
-                    WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
+                    // 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 the WebView dark mode.
-                    WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
+                    // Turn on algorithmic darkening.
+                    WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
                 }
             }
         }
@@ -5094,7 +4984,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);
 
@@ -5104,8 +4994,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.
@@ -5121,6 +5014,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                     swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
                                 }
                             }
+                        } 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.
@@ -5139,23 +5044,32 @@ 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);
-                                }
+                        // 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);
                             }
                         }
 
@@ -5169,6 +5083,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.
@@ -5202,7 +5140,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Get the file name from the content disposition.
-                String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
+                String fileNameString = PrepareSaveDialogCoroutine.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
 
                 // Instantiate the save dialog.
                 DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, formattedFileSizeString, fileNameString, userAgent,
@@ -5214,7 +5152,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     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)));
+                    pendingDialogsArrayList.add(new PendingDialogDataClass(saveDialogFragment, getString(R.string.save_dialog)));
                 }
             }
         });
@@ -5242,7 +5180,7 @@ 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.
+        // Process scroll changes.
         nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
             // Set the swipe to refresh status.
             if (nestedScrollWebView.getSwipeToRefresh()) {
@@ -5253,23 +5191,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 swipeRefreshLayout.setEnabled(false);
             }
 
-            //  Scroll the bottom app bar if enabled.
-            if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) {
-                if (scrollY < oldScrollY) {  // The WebView was scrolled down.
-                    // Animate the bottom app bar onto the screen.
-                    objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
-
-                    // Make it so.
-                    objectAnimator.start();
-                } else if (scrollY > oldScrollY) {  // The WebView was scrolled up.
-                    // Animate the bottom app bar off the screen.
-                    objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight());
-
-                    // Make it so.
-                    objectAnimator.start();
-                }
-            }
-
             // 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) {
@@ -5311,10 +5232,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Set the favorite icon when it changes.
             @Override
             public void onReceivedIcon(WebView view, Bitmap icon) {
-                // Only update the favorite icon if the website has finished loading.
-                if (progressBar.getVisibility() == View.GONE) {
+                // Only update the favorite icon if the website has finished loading and the new favorite icon height is greater than the current favorite icon height.  This prevents low resolution icons from replacing high resolution one.
+                if ((progressBar.getVisibility() == View.GONE) && (icon.getHeight() > nestedScrollWebView.getFavoriteIconHeight())) {
                     // Store the new favorite icon.
-                    nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
+                    nestedScrollWebView.setFavoriteIcon(icon);
 
                     // Get the current page position.
                     int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
@@ -5425,8 +5346,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // 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);
+                    // Launch the file chooser intent.
+                    browseFileUploadActivityResultLauncher.launch(fileChooserIntent);
                 } 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);
@@ -5437,8 +5358,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Set the file type to everything.
                     genericFileChooserIntent.setType("*/*");
 
-                    // Start the generic file chooser intent.
-                    startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
+                    // Launch the generic file chooser intent.
+                    browseFileUploadActivityResultLauncher.launch(genericFileChooserIntent);
                 }
                 return true;
             }
@@ -5882,8 +5803,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.
@@ -5892,9 +5826,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);
 
@@ -5930,21 +5861,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get a URI for the current URL.
                 Uri currentUri = Uri.parse(url);
 
-                // Get the IP addresses for the host.
-                new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
+                // Get the current domain name.
+                String currentDomainName = currentUri.getHost();
+
+                if ((currentDomainName != null) && !currentDomainName.isEmpty()) {
+                    // Get the IP addresses for the current URI.
+                    GetHostIpAddressesCoroutine.getAddresses(currentDomainName, nestedScrollWebView, getSupportFragmentManager(), getString(R.string.pinned_mismatch));
+                }
 
                 // Replace Refresh with Stop if the options menu has been created.  (The first WebView typically begins loading before the menu items are instantiated.)
                 if (optionsMenu != null) {
                     // 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);
-
-                    // Set the icon if it is displayed in the AppBar.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
-                    if (displayAdditionalAppBarIcons) {
+                    // Set the icon if it is displayed in the AppBar.
+                    if (displayAdditionalAppBarIcons)
                         optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
-                    }
                 }
             }
 
@@ -5960,16 +5892,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) {
-                        // Set the icon.
+                    // 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.
@@ -5980,16 +5911,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.
                     }
@@ -6003,6 +5926,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());
 
@@ -6095,11 +6026,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]) &&
@@ -6123,7 +6054,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         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)));
+                        pendingDialogsArrayList.add(new PendingDialogDataClass(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
                     }
                 }
             }
@@ -6212,4 +6143,4 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         }
     }
-}
\ No newline at end of file
+}