]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Replace all instances of `startActivityForResult()` with `registerForActivityResult...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index cd5a3d8444e6ac6cae3de63abe226dae5fd71066..461c7405e59c0e9dd6c76e2a7e2420f3f14c1263 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>.
  *
@@ -52,7 +52,6 @@ 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 +98,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 +115,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;
@@ -136,7 +138,6 @@ import com.stoutner.privacybrowser.dataclasses.PendingDialog;
 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;
@@ -174,10 +175,8 @@ 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;
@@ -186,14 +185,13 @@ 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,
+        PopulateBlocklists.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<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
     public static String proxyMode = ProxyHelper.NONE;
 
     // Declare the public static variables.
@@ -212,15 +210,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;
@@ -236,9 +231,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
     private NestedScrollWebView currentWebView;
 
-    // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
-    private final Map<String, String> customHeaders = new HashMap<>();
-
     // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
     private String searchURL;
 
@@ -264,9 +256,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;
 
@@ -282,7 +271,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private SanitizeUrlHelper sanitizeUrlHelper;
 
     // Declare the class variables
+    private boolean bookmarksDrawerPinned;
     private boolean bottomAppBar;
+    private boolean displayAdditionalAppBarIcons;
     private boolean displayingFullScreenVideo;
     private boolean downloadWithExternalApp;
     private boolean fullScreenBrowsingModeEnabled;
@@ -304,19 +295,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;
@@ -373,7 +365,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) {
@@ -388,17 +380,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 {
@@ -423,29 +434,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
@@ -453,19 +446,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) {
@@ -477,6 +470,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
@@ -490,6 +493,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);
@@ -503,9 +507,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);
@@ -551,6 +556,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.
@@ -591,6 +597,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());
 
@@ -606,12 +615,54 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         proxyHelper = new ProxyHelper();
         sanitizeUrlHelper = new SanitizeUrlHelper();
 
+        // Update the bookmarks drawer pinned image view.
+        updateBookmarksDrawerPinnedImageView();
+
         // Initialize the app.
         initializeApp();
 
         // Apply the app settings from the shared preferences.
         applyAppSettings();
 
+        // Control what the system back command does.
+        OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
+            @Override
+            public void handleOnBackPressed() {
+                // Process the different back options.
+                if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
+                    // Close the navigation drawer.
+                    drawerLayout.closeDrawer(GravityCompat.START);
+                } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
+                    // close the bookmarks drawer.
+                    drawerLayout.closeDrawer(GravityCompat.END);
+                } else if (displayingFullScreenVideo) {  // A full screen video is shown.
+                    // Exit the full screen video.
+                    exitFullScreenVideo();
+                } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
+                    // Get the current web back forward list.
+                    WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
+
+                    // Get the previous entry URL.
+                    String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
+
+                    // Apply the domain settings.
+                    applyDomainSettings(currentWebView, previousUrl, false, false, false);
+
+                    // Go back.
+                    currentWebView.goBack();
+                } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
+                    // Close the current tab.
+                    closeCurrentTab();
+                } else {  // There isn't anything to do in Privacy Browser.
+                    // Run clear and exit.
+                    clearAndExit();
+                }
+            }
+        };
+
+        // Register the on back pressed callback.
+        getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
+
         // Populate the blocklists.
         populateBlocklists = new PopulateBlocklists(this, this).execute();
     }
@@ -671,7 +722,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;
 
@@ -883,10 +934,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
@@ -978,11 +1030,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) {
@@ -1061,10 +1110,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.
@@ -1688,7 +1742,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();
@@ -1741,17 +1795,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;
@@ -1905,19 +1951,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();
@@ -1935,26 +1981,29 @@ 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();
 
+                // 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);
 
@@ -1962,10 +2011,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();
@@ -1983,14 +2032,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.
@@ -2128,8 +2177,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();
@@ -2181,6 +2230,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);
@@ -2660,188 +2718,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();
@@ -3091,19 +2974,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) -> {
@@ -3407,14 +3281,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;
@@ -3424,14 +3300,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);
+
+                // Move to the first entry in the cursor.
+                bookmarksCursor.moveToFirst();
+
+                // 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)));
 
-                // Instantiate the edit folder bookmark dialog.
-                DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
+                    // Move to the next bookmark.
+                    bookmarksCursor.moveToNext();
+                }
 
-                // Show the edit folder bookmark dialog.
-                editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
+                // Close the cursor.
+                bookmarksCursor.close();
             } else {  // The bookmark is not a folder.
                 // Get the bookmark cursor for this ID.
                 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
@@ -3439,13 +3324,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(drawerLayout, 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;
         });
@@ -3492,9 +3381,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);
 
@@ -3513,13 +3399,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);
+        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("proxy", getString(R.string.proxy_default_value));
-        fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
+        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.
@@ -3532,14 +3418,13 @@ 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);
@@ -3786,12 +3671,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);
@@ -3822,12 +3707,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));
@@ -3900,7 +3787,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:
@@ -3935,8 +3822,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);
@@ -3947,8 +3837,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:
@@ -3957,43 +3851,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;
                     }
                 }
@@ -4032,17 +3921,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());
@@ -4066,8 +3955,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);
@@ -4093,7 +3985,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:
@@ -4101,27 +3993,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);
                     }
                 }
 
@@ -4149,7 +4035,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);
         }
     }
 
@@ -4240,27 +4126,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 PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                            }
                         }
                     }
                 }
@@ -4591,7 +4481,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;
 
@@ -4719,7 +4609,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();
@@ -4729,7 +4619,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);
 
@@ -4748,7 +4638,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();
@@ -4776,7 +4666,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();
 
@@ -4808,7 +4698,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.
@@ -4834,7 +4724,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();
@@ -4871,9 +4761,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) {
@@ -4908,6 +4795,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);
@@ -5004,44 +4907,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);
                 }
             }
         }
@@ -5198,7 +5101,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Calculate the Y change.
                     float motionY = motionEvent2.getY() - motionEvent1.getY();
 
-                    // Scroll the app bar if the change is greater than 100 pixels.
+                    // 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);
@@ -5453,8 +5356,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);
@@ -5465,8 +5368,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;
             }
@@ -5976,13 +5879,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Set the title.
                     optionsRefreshMenuItem.setTitle(R.string.stop);
 
-                    // Get the app bar and theme preferences.
-                    boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
-
-                    // 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);
-                    }
                 }
             }
 
@@ -5998,16 +5897,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.
@@ -6018,16 +5916,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.
                     }
@@ -6041,6 +5931,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());
 
@@ -6250,4 +6148,4 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         }
     }
-}
\ No newline at end of file
+}