]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Bump the minimum API to 24 (Android 7).
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index 637e9a52ae572e9ae4a3d0d40936aeb7af71c444..39b95dca65d30d4ab5f35a3b31b1d47fd6969873 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2015-2023 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
@@ -46,7 +46,6 @@ import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
 import android.net.http.SslError;
-import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
@@ -57,7 +56,6 @@ import android.print.PrintManager;
 import android.provider.DocumentsContract;
 import android.provider.OpenableColumns;
 import android.text.Editable;
-import android.text.Spanned;
 import android.text.TextWatcher;
 import android.text.style.ForegroundColorSpan;
 import android.util.Patterns;
@@ -129,12 +127,12 @@ import com.google.android.material.tabs.TabLayout;
 
 import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
-import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
-import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
-import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
-import com.stoutner.privacybrowser.dataclasses.PendingDialog;
+import com.stoutner.privacybrowser.coroutines.GetHostIpAddressesCoroutine;
+import com.stoutner.privacybrowser.coroutines.PopulateBlocklistsCoroutine;
+import com.stoutner.privacybrowser.coroutines.PrepareSaveDialogCoroutine;
+import com.stoutner.privacybrowser.dataclasses.PendingDialogDataClass;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
@@ -154,6 +152,7 @@ import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.ProxyHelper;
 import com.stoutner.privacybrowser.helpers.SanitizeUrlHelper;
+import com.stoutner.privacybrowser.helpers.UrlHelper;
 import com.stoutner.privacybrowser.views.NestedScrollWebView;
 
 import java.io.ByteArrayInputStream;
@@ -186,12 +185,12 @@ import kotlin.Pair;
 
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
         FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener,
-        PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
+        PopulateBlocklistsCoroutine.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
 
     // Define the public static variables.
     public static final ExecutorService executorService = Executors.newFixedThreadPool(4);
     public static String orbotStatus = "unknown";
-    public static final ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
+    public static final ArrayList<PendingDialogDataClass> pendingDialogsArrayList =  new ArrayList<>();
     public static String proxyMode = ProxyHelper.NONE;
 
     // Declare the public static variables.
@@ -223,10 +222,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private int savedTabPosition;
     private String savedProxyMode;
 
-    // Define the class variables.
-    @SuppressWarnings("rawtypes")
-    AsyncTask populateBlocklists;
-
     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
     private NestedScrollWebView currentWebView;
@@ -245,11 +240,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
     private ActionBarDrawerToggle actionBarDrawerToggle;
 
-    // The color spans are used in `onCreate()` and `highlightUrlText()`.
-    private ForegroundColorSpan redColorSpan;
-    private ForegroundColorSpan initialGrayColorSpan;
-    private ForegroundColorSpan finalGrayColorSpan;
-
     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     private Cursor bookmarksCursor;
 
@@ -268,7 +258,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
     private DomainsDatabaseHelper domainsDatabaseHelper;
     private ProxyHelper proxyHelper;
-    private SanitizeUrlHelper sanitizeUrlHelper;
 
     // Declare the class variables
     private boolean bookmarksDrawerPinned;
@@ -276,14 +265,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private boolean displayAdditionalAppBarIcons;
     private boolean displayingFullScreenVideo;
     private boolean downloadWithExternalApp;
+    private ForegroundColorSpan finalGrayColorSpan;
     private boolean fullScreenBrowsingModeEnabled;
     private boolean hideAppBar;
-    private boolean incognitoModeEnabled;
     private boolean inFullScreenBrowsingMode;
+    private boolean incognitoModeEnabled;
+    private ForegroundColorSpan initialGrayColorSpan;
     private boolean loadingNewIntent;
     private BroadcastReceiver orbotStatusBroadcastReceiver;
     private boolean reapplyAppSettingsOnRestart;
     private boolean reapplyDomainSettingsOnRestart;
+    private ForegroundColorSpan redColorSpan;
     private boolean sanitizeAmpRedirects;
     private boolean sanitizeTrackingQueries;
     private boolean scrollAppBar;
@@ -490,16 +482,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Populate the result launcher activity.  This will no longer be needed once the activity has transitioned to Kotlin.
         resultLauncherActivityHandle = this;
 
-        // Check to see if the activity has been restarted.
-        if (savedInstanceState != null) {
-            // Store the saved instance state variables.
-            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);
-            savedProxyMode = savedInstanceState.getString(PROXY_MODE);
-        }
-
         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
 
@@ -515,6 +497,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Get the theme entry values string array.
         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
 
+        // Get the current theme status.
+        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
         // Set the app theme according to the preference.  A switch statement cannot be used because the theme entry values string array is not a compile time constant.
         if (appTheme.equals(appThemeEntryValuesStringArray[1])) {  // The light theme is selected.
             // Apply the light theme.
@@ -532,140 +517,161 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         }
 
-        // Disable screenshots if not allowed.
-        if (!allowScreenshots) {
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
+        // Do not continue if the app theme is different than the OS theme.  The app always initially starts in the OS theme.
+        // If the user has specified the opposite theme should be used, the app will restart in that mode after the above `setDefaultNightMode()` code processes.  However, the restart is delayed.
+        // If the blacklist coroutine starts below it will continue to run during the restart, which leads to indeterminate behavior, with the system often not knowing how many tabs exist.
+        // See https://redmine.stoutner.com/issues/952.
+        if (appTheme.equals(appThemeEntryValuesStringArray[0]) ||  // The system default theme is used.
+                (appTheme.equals(appThemeEntryValuesStringArray[1]) && currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) ||  // The app is running in day theme as desired.
+                (appTheme.equals(appThemeEntryValuesStringArray[2]) && currentThemeStatus == Configuration.UI_MODE_NIGHT_YES)) {  // The app is running in night theme as desired.
+
+            // Disable screenshots if not allowed.
+            if (!allowScreenshots) {
+                getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+            }
 
-        // Enable the drawing of the entire webpage.  This makes it possible to save a website image.  This must be done before anything else happens with the WebView.
-        WebView.enableSlowWholeDocumentDraw();
+            // 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);
+                savedProxyMode = savedInstanceState.getString(PROXY_MODE);
+            }
 
-        // Set the content view according to the position of the app bar.
-        if (bottomAppBar) setContentView(R.layout.main_framelayout_bottom_appbar);
-        else setContentView(R.layout.main_framelayout_top_appbar);
+            // Enable the drawing of the entire webpage.  This makes it possible to save a website image.  This must be done before anything else happens with the WebView.
+            WebView.enableSlowWholeDocumentDraw();
 
-        // Get handles for the views.
-        rootFrameLayout = findViewById(R.id.root_framelayout);
-        drawerLayout = findViewById(R.id.drawerlayout);
-        coordinatorLayout = findViewById(R.id.coordinatorlayout);
-        appBarLayout = findViewById(R.id.appbar_layout);
-        toolbar = findViewById(R.id.toolbar);
-        findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
-        tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
-        tabLayout = findViewById(R.id.tablayout);
-        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);
+            // Set the content view according to the position of the app bar.
+            if (bottomAppBar) setContentView(R.layout.main_framelayout_bottom_appbar);
+            else setContentView(R.layout.main_framelayout_top_appbar);
 
-        // Get a handle for the navigation menu.
-        Menu navigationMenu = navigationView.getMenu();
+            // Get handles for the views.
+            rootFrameLayout = findViewById(R.id.root_framelayout);
+            drawerLayout = findViewById(R.id.drawerlayout);
+            coordinatorLayout = findViewById(R.id.coordinatorlayout);
+            appBarLayout = findViewById(R.id.appbar_layout);
+            toolbar = findViewById(R.id.toolbar);
+            findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+            tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
+            tabLayout = findViewById(R.id.tablayout);
+            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 handles for the navigation menu items.
-        navigationBackMenuItem = navigationMenu.findItem(R.id.back);
-        navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
-        navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
-        navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
+            // Get a handle for the navigation menu.
+            Menu navigationMenu = navigationView.getMenu();
 
-        // Listen for touches on the navigation menu.
-        navigationView.setNavigationItemSelectedListener(this);
+            // Get handles for the navigation menu items.
+            navigationBackMenuItem = navigationMenu.findItem(R.id.back);
+            navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
+            navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
+            navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
 
-        // Get a handle for the app compat delegate.
-        AppCompatDelegate appCompatDelegate = getDelegate();
+            // Listen for touches on the navigation menu.
+            navigationView.setNavigationItemSelectedListener(this);
 
-        // Set the support action bar.
-        appCompatDelegate.setSupportActionBar(toolbar);
+            // Get a handle for the app compat delegate.
+            AppCompatDelegate appCompatDelegate = getDelegate();
 
-        // Get a handle for the action bar.
-        actionBar = appCompatDelegate.getSupportActionBar();
+            // Set the support action bar.
+            appCompatDelegate.setSupportActionBar(toolbar);
 
-        // Remove the incorrect lint warning below that the action bar might be null.
-        assert actionBar != null;
+            // Get a handle for the action bar.
+            actionBar = appCompatDelegate.getSupportActionBar();
 
-        // Add the custom layout, which shows the URL text bar.
-        actionBar.setCustomView(R.layout.url_app_bar);
-        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+            // Remove the incorrect lint warning below that the action bar might be null.
+            assert actionBar != null;
 
-        // Get handles for the views in the URL app bar.
-        urlRelativeLayout = findViewById(R.id.url_relativelayout);
-        urlEditText = findViewById(R.id.url_edittext);
+            // Add the custom layout, which shows the URL text bar.
+            actionBar.setCustomView(R.layout.url_app_bar);
+            actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
-        // Create the hamburger icon at the start of the AppBar.
-        actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
+            // Get handles for the views in the URL app bar.
+            urlRelativeLayout = findViewById(R.id.url_relativelayout);
+            urlEditText = findViewById(R.id.url_edittext);
 
-        // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
-        drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+            // Create the hamburger icon at the start of the AppBar.
+            actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
 
-        // Initially hide the user interface so that only the blocklist loading screen is shown (if reloading).
-        drawerLayout.setVisibility(View.GONE);
+            // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
+            drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
 
-        // Initialize the web view pager adapter.
-        webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
+            // Initially hide the user interface so that only the blocklist loading screen is shown (if reloading).
+            drawerLayout.setVisibility(View.GONE);
 
-        // Set the pager adapter on the web view pager.
-        webViewPager.setAdapter(webViewPagerAdapter);
+            // Initialize the web view pager adapter.
+            webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
 
-        // Store up to 100 tabs in memory.
-        webViewPager.setOffscreenPageLimit(100);
+            // Set the pager adapter on the web view pager.
+            webViewPager.setAdapter(webViewPagerAdapter);
 
-        // Instantiate the helpers.
-        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
-        domainsDatabaseHelper = new DomainsDatabaseHelper(this);
-        proxyHelper = new ProxyHelper();
-        sanitizeUrlHelper = new SanitizeUrlHelper();
+            // Store up to 100 tabs in memory.
+            webViewPager.setOffscreenPageLimit(100);
 
-        // Update the bookmarks drawer pinned image view.
-        updateBookmarksDrawerPinnedImageView();
+            // Instantiate the helpers.
+            bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
+            domainsDatabaseHelper = new DomainsDatabaseHelper(this);
+            proxyHelper = new ProxyHelper();
 
-        // Initialize the app.
-        initializeApp();
+            // Update the bookmarks drawer pinned image view.
+            updateBookmarksDrawerPinnedImageView();
 
-        // Apply the app settings from the shared preferences.
-        applyAppSettings();
+            // Initialize the app.
+            initializeApp();
 
-        // Control what the system back command does.
-        OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
-            @Override
-            public void handleOnBackPressed() {
-                // Process the different back options.
-                if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
-                    // Close the navigation drawer.
-                    drawerLayout.closeDrawer(GravityCompat.START);
-                } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
-                    // close the bookmarks drawer.
-                    drawerLayout.closeDrawer(GravityCompat.END);
-                } else if (displayingFullScreenVideo) {  // A full screen video is shown.
-                    // Exit the full screen video.
-                    exitFullScreenVideo();
-                    // It shouldn't be possible for the currentWebView to be null, but crash logs indicate it sometimes happens.
-                } else if ((currentWebView != null) && (currentWebView.canGoBack())) {  // There is at least one item in the current WebView history.
-                    // Get the current web back forward list.
-                    WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
-
-                    // Get the previous entry URL.
-                    String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
-
-                    // Apply the domain settings.
-                    applyDomainSettings(currentWebView, previousUrl, false, false, false);
-
-                    // Go back.
-                    currentWebView.goBack();
-                } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
-                    // Close the current tab.
-                    closeCurrentTab();
-                } else {  // There isn't anything to do in Privacy Browser.
-                    // Run clear and exit.
-                    clearAndExit();
+            // Apply the app settings from the shared preferences.
+            applyAppSettings();
+
+            // Control what the system back command does.
+            OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
+                @Override
+                public void handleOnBackPressed() {
+                    // Process the different back options.
+                    if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
+                        // Close the navigation drawer.
+                        drawerLayout.closeDrawer(GravityCompat.START);
+                    } else if (drawerLayout.isDrawerVisible(GravityCompat.END)) {  // The bookmarks drawer is open.
+                        // close the bookmarks drawer.
+                        drawerLayout.closeDrawer(GravityCompat.END);
+                    } else if (displayingFullScreenVideo) {  // A full screen video is shown.
+                        // Exit the full screen video.
+                        exitFullScreenVideo();
+                        // It shouldn't be possible for the currentWebView to be null, but crash logs indicate it sometimes happens.
+                    } else if ((currentWebView != null) && (currentWebView.canGoBack())) {  // There is at least one item in the current WebView history.
+                        // Get the current web back forward list.
+                        WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
+
+                        // Get the previous entry URL.
+                        String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
+
+                        // Apply the domain settings.
+                        applyDomainSettings(currentWebView, previousUrl, false, false, false);
+
+                        // Go back.
+                        currentWebView.goBack();
+                    } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
+                        // Close the current tab.
+                        closeCurrentTab();
+                    } else {  // There isn't anything to do in Privacy Browser.
+                        // Run clear and exit.
+                        clearAndExit();
+                    }
                 }
-            }
-        };
+            };
+
+            // Register the on back pressed callback.
+            getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
 
-        // Register the on back pressed callback.
-        getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
+            // Instantiate the populate blocklists coroutine.
+            PopulateBlocklistsCoroutine populateBlocklistsCoroutine = new PopulateBlocklistsCoroutine(this);
 
-        // Populate the blocklists.
-        populateBlocklists = new PopulateBlocklists(this, this).execute();
+            // Populate the blocklists.
+            populateBlocklistsCoroutine.populateBlocklists(this);
+        }
     }
 
     @Override
@@ -745,6 +751,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
             }
         } else {  // The app has been restarted.
+            // Set the saved tab position to be the size of the saved state array list.  The tab position is 0 based, meaning the at the new tab will be the tab position that is restored.
+            savedTabPosition = savedStateArrayList.size();
+
             // Replace the intent that started the app with this one.  This will load the tab after the others have been restored.
             setIntent(intent);
         }
@@ -815,21 +824,23 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the default commands.
         super.onStart();
 
-        // Resume any WebViews.
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+        // Resume any WebViews if the pager adapter exists.  If the app is restarting to change the initial app theme it won't have been populated yet.
+        if (webViewPagerAdapter != null) {
+            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
 
-            // Only resume the WebViews if they exist (they won't when the app is first created).
-            if (fragmentView != null) {
-                // Get the nested scroll WebView from the tab fragment.
-                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+                // Only resume the WebViews if they exist (they won't when the app is first created).
+                if (fragmentView != null) {
+                    // Get the nested scroll WebView from the tab fragment.
+                    NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                // Resume the nested scroll WebView.
-                nestedScrollWebView.onResume();
+                    // Resume the nested scroll WebView.
+                    nestedScrollWebView.onResume();
+                }
             }
         }
 
@@ -858,10 +869,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Show any pending dialogs.
         for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
             // Get the pending dialog from the array list.
-            PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
+            PendingDialogDataClass pendingDialogDataClass = pendingDialogsArrayList.get(i);
 
             // Show the pending dialog.
-            pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
+            pendingDialogDataClass.dialogFragment.show(getSupportFragmentManager(), pendingDialogDataClass.tag);
         }
 
         // Clear the pending dialogs array list.
@@ -874,20 +885,24 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the default commands.
         super.onStop();
 
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+        // Only pause the WebViews if the pager adapter is not null, which is the case if the app is restarting to change the initial app theme.
+        if (webViewPagerAdapter != null) {
+            // Pause each web view.
+            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
 
-            // Only pause the WebViews if they exist (they won't when the app is first created).
-            if (fragmentView != null) {
-                // Get the nested scroll WebView from the tab fragment.
-                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+                // Only pause the WebViews if they exist (they won't when the app is first created).
+                if (fragmentView != null) {
+                    // Get the nested scroll WebView from the tab fragment.
+                    NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                // Pause the nested scroll WebView.
-                nestedScrollWebView.onPause();
+                    // Pause the nested scroll WebView.
+                    nestedScrollWebView.onPause();
+                }
             }
         }
 
@@ -902,44 +917,47 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the default commands.
         super.onSaveInstanceState(savedInstanceState);
 
-        // Create the saved state array lists.
-        ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
-        ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
+        // Only save the instance state if the WebView pager adapter is not null, which will be the case if the app is restarting to change the initial app theme.
+        if (webViewPagerAdapter != null) {
+            // Create the saved state array lists.
+            ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
+            ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
 
-        // Get the URLs from each tab.
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+            // Get the URLs from each tab.
+            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
 
-            if (fragmentView != null) {
-                // Get the nested scroll WebView from the tab fragment.
-                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+                if (fragmentView != null) {
+                    // Get the nested scroll WebView from the tab fragment.
+                    NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                // Create saved state bundle.
-                Bundle savedStateBundle = new Bundle();
+                    // Create saved state bundle.
+                    Bundle savedStateBundle = new Bundle();
 
-                // Get the current states.
-                nestedScrollWebView.saveState(savedStateBundle);
-                Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
+                    // Get the current states.
+                    nestedScrollWebView.saveState(savedStateBundle);
+                    Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
 
-                // Store the saved states in the array lists.
-                savedStateArrayList.add(savedStateBundle);
-                savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
+                    // Store the saved states in the array lists.
+                    savedStateArrayList.add(savedStateBundle);
+                    savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
+                }
             }
-        }
 
-        // Get the current tab position.
-        int currentTabPosition = tabLayout.getSelectedTabPosition();
+            // Get the current tab position.
+            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);
+            // 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);
+        }
     }
 
     @Override
@@ -959,11 +977,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             bookmarksDatabaseHelper.close();
         }
 
-        // Stop populating the blocklists if the AsyncTask is running in the background.
-        if (populateBlocklists != null) {
-            populateBlocklists.cancel(true);
-        }
-
         // Run the default commands.
         super.onDestroy();
     }
@@ -1856,8 +1869,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
             } else {  // Handle the download inside of Privacy Browser.
                 // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                        currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
+                PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptCookies());
             }
 
             // Consume the event.
@@ -1877,7 +1890,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         } else if (menuItemId == R.id.add_to_homescreen) {  // Add to homescreen.
             // Instantiate the create home screen shortcut dialog.
             DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
-                    currentWebView.getFavoriteOrDefaultIcon());
+                    currentWebView.getFavoriteIcon());
 
             // Show the create home screen shortcut dialog.
             createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
@@ -2272,7 +2285,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         super.onPostCreate(savedInstanceState);
 
         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
-        actionBarDrawerToggle.syncState();
+        // If the app is restarting to change the app theme the action bar drawer toggle will not yet be populated.
+        if (actionBarDrawerToggle != null)
+            actionBarDrawerToggle.syncState();
     }
 
     @Override
@@ -2353,8 +2368,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         downloadUrlWithExternalApp(linkUrl);
                     } else {  // Handle the download inside of Privacy Browser.
                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2425,8 +2439,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         downloadUrlWithExternalApp(imageUrl);
                     } else {  // Handle the download inside of Privacy Browser.
                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2530,8 +2543,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         downloadUrlWithExternalApp(imageUrl);
                     } else {  // Handle the download inside of Privacy Browser.
                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), imageUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2557,8 +2569,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         downloadUrlWithExternalApp(linkUrl);
                     } else {  // Handle the download inside of Privacy Browser.
                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
-                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                        PrepareSaveDialogCoroutine.prepareSaveDialog(this, getSupportFragmentManager(), linkUrl, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies());
                     }
 
                     // Consume the event.
@@ -2983,7 +2994,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Remove the formatting from the URL edit text when the user is editing the text.
         urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
             if (hasFocus) {  // The user is editing the URL text box.
-                // Remove the highlighting.
+                // Remove the syntax highlighting.
                 urlEditText.getText().removeSpan(redColorSpan);
                 urlEditText.getText().removeSpan(initialGrayColorSpan);
                 urlEditText.getText().removeSpan(finalGrayColorSpan);
@@ -2991,8 +3002,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Move to the beginning of the string.
                 urlEditText.setSelection(0);
 
-                // Reapply the highlighting.
-                highlightUrlText();
+                // Reapply the syntax highlighting.
+                UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan);
             }
         });
 
@@ -3135,7 +3146,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             @Override
             public void onTabReselected(TabLayout.Tab tab) {
                 // Instantiate the View SSL Certificate dialog.
-                DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId(), currentWebView.getFavoriteOrDefaultIcon());
+                DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId(), currentWebView.getFavoriteIcon());
 
                 // Display the View SSL Certificate dialog.
                 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
@@ -3151,7 +3162,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
         launchBookmarksActivityFab.setOnClickListener(v -> {
             // Get a copy of the favorite icon bitmap.
-            Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
+            Bitmap favoriteIconBitmap = currentWebView.getFavoriteIcon();
 
             // Create a favorite icon byte array output stream.
             ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
@@ -3178,7 +3189,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the create new bookmark folder FAB to display an alert dialog.
         createBookmarkFolderFab.setOnClickListener(v -> {
             // Create a create bookmark folder dialog.
-            DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
+            DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteIcon());
 
             // Show the create bookmark folder dialog.
             createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
@@ -3187,7 +3198,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the create new bookmark FAB to display an alert dialog.
         createBookmarkFab.setOnClickListener(view -> {
             // Instantiate the create bookmark dialog.
-            DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
+            DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteIcon());
 
             // Display the create bookmark dialog.
             createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
@@ -3610,7 +3621,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
 
                     // Set the default favorite icon as the favorite icon for this tab.
-                    tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
+                    tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteIcon(), 64, 64, true));
 
                     // Set the loading title text.
                     tabTitleTextView.setText(R.string.loading);
@@ -4098,7 +4109,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
                             } catch (Exception waitingForTorException) {
                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                                pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
+                                pendingDialogsArrayList.add(new PendingDialogDataClass(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
                             }
                         }
                     }
@@ -4114,7 +4125,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
                         } catch (Exception orbotNotInstalledException) {
                             // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                            pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                            pendingDialogsArrayList.add(new PendingDialogDataClass(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
                         }
                     }
                 }
@@ -4150,7 +4161,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                 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)));
+                                pendingDialogsArrayList.add(new PendingDialogDataClass(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
                             }
                         }
                     }
@@ -4224,64 +4235,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
-    private void highlightUrlText() {
-        // Only highlight the URL text if the box is not currently selected.
-        if (!urlEditText.hasFocus()) {
-            // Get the URL string.
-            String urlString = urlEditText.getText().toString();
-
-            // Highlight the URL according to the protocol.
-            if (urlString.startsWith("file://") || urlString.startsWith("content://")) {  // This is a file or content URL.
-                // De-emphasize everything before the file name.
-                urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-            } else {  // This is a web URL.
-                // Get the index of the `/` immediately after the domain name.
-                int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
-
-                // Create a base URL string.
-                String baseUrl;
-
-                // Get the base URL.
-                if (endOfDomainName > 0) {  // There is at least one character after the base URL.
-                    // Get the base URL.
-                    baseUrl = urlString.substring(0, endOfDomainName);
-                } else {  // There are no characters after the base URL.
-                    // Set the base URL to be the entire URL string.
-                    baseUrl = urlString;
-                }
-
-                // Get the index of the last `.` in the domain.
-                int lastDotIndex = baseUrl.lastIndexOf(".");
-
-                // Get the index of the penultimate `.` in the domain.
-                int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
-
-                // Markup the beginning of the URL.
-                if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
-                    urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-
-                    // De-emphasize subdomains.
-                    if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
-                        urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                    }
-                } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
-                    if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
-                        // De-emphasize the protocol and the additional subdomains.
-                        urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                    } else {  // There is only one subdomain in the domain name.
-                        // De-emphasize only the protocol.
-                        urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                    }
-                }
-
-                // De-emphasize the text after the domain name.
-                if (endOfDomainName > 0) {
-                    urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-            }
-        }
-    }
-
     private void loadBookmarksFolder() {
         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
@@ -4382,11 +4335,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private String sanitizeUrl(String url) {
         // Sanitize tracking queries.
         if (sanitizeTrackingQueries)
-            url = sanitizeUrlHelper.sanitizeTrackingQueries(url);
+            url = SanitizeUrlHelper.sanitizeTrackingQueries(url);
 
         // Sanitize AMP redirects.
         if (sanitizeAmpRedirects)
-            url = sanitizeUrlHelper.sanitizeAmpRedirects(url);
+            url = SanitizeUrlHelper.sanitizeAmpRedirects(url);
 
         // Return the sanitized URL.
         return url;
@@ -4406,7 +4359,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Add the first tab.
             addNewTab("", true);
         } else {  // The activity has been restarted.
-            // Restore each tab.  Once the minimum API >= 24, a `forEach()` command can be used.
+            // Restore each tab.
             for (int i = 0; i < savedStateArrayList.size(); i++) {
                 // Add a new tab.
                 tabLayout.addTab(tabLayout.newTab());
@@ -4432,7 +4385,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             if (savedTabPosition == 0) {  // The first tab is selected.
                 // Set the first page as the current WebView.
                 setCurrentWebView(0);
-            } else {  // the first tab is not selected.
+            } else {  // The first tab is not selected.
                 // Move to the selected tab.
                 webViewPager.setCurrentItem(savedTabPosition);
             }
@@ -4875,8 +4828,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Display the current URL in the URL text box.
                     urlEditText.setText(url);
 
-                    // Highlight the URL text.
-                    highlightUrlText();
+                    // Highlight the URL syntax.
+                    UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan);
                 }
             } else {  // A new intent is being loaded.
                 // Reset the loading new intent tracker.
@@ -4891,7 +4844,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Remove any background on the URL relative layout.
                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
             }
-        } else {  // The fragment has not been populated.  Try again in 100 milliseconds.
+        } else if (pageNumber == savedTabPosition){  // The app is being restored but the saved tab position fragment has not been populated yet.  Try again in 100 milliseconds.
             // Create a handler to set the current WebView.
             Handler setCurrentWebViewHandler = new Handler();
 
@@ -5151,10 +5104,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
 
                 // Get the file name from the content disposition.
-                String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
+                String fileNameString = UrlHelper.getFileName(this, contentDisposition, mimetype, downloadUrl);
 
                 // Instantiate the save dialog.
-                DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+                DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, fileNameString, formattedFileSizeString, userAgent,
                         nestedScrollWebView.getAcceptCookies());
 
                 // Try to show the dialog.  The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
@@ -5163,7 +5116,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
                 } catch (Exception exception) {  // The dialog could not be shown.
                     // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                    pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog)));
+                    pendingDialogsArrayList.add(new PendingDialogDataClass(saveDialogFragment, getString(R.string.save_dialog)));
                 }
             }
         });
@@ -5243,10 +5196,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Set the favorite icon when it changes.
             @Override
             public void onReceivedIcon(WebView view, Bitmap icon) {
-                // Only update the favorite icon if the website has finished loading.
-                if (progressBar.getVisibility() == View.GONE) {
+                // Only update the favorite icon if the website has finished loading and the new favorite icon height is greater than the current favorite icon height.
+                // This prevents low resolution icons from replacing high resolution one.
+                // The check for the visibility of the progress bar can possibly be removed once https://redmine.stoutner.com/issues/747 is fixed.
+                if ((progressBar.getVisibility() == View.GONE) && (icon.getHeight() > nestedScrollWebView.getFavoriteIconHeight())) {
                     // Store the new favorite icon.
-                    nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
+                    nestedScrollWebView.setFavoriteIcon(icon);
 
                     // Get the current page position.
                     int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
@@ -5378,9 +5333,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         nestedScrollWebView.setWebViewClient(new WebViewClient() {
             // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
-            // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
             @Override
-            public boolean shouldOverrideUrlLoading(WebView view, String url) {
+            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest webResourceRequest) {
+                // Get the URL from the web resource request.
+                String url = webResourceRequest.getUrl().toString();
+
                 // Sanitize the url.
                 url = sanitizeUrl(url);
 
@@ -5859,8 +5816,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Display the formatted URL text.
                     urlEditText.setText(url);
 
-                    // Apply text highlighting to the URL text box.
-                    highlightUrlText();
+                    // Highlight the URL syntax.
+                    UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan);
 
                     // Hide the keyboard.
                     inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
@@ -5872,8 +5829,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Get a URI for the current URL.
                 Uri currentUri = Uri.parse(url);
 
-                // Get the IP addresses for the host.
-                new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
+                // Get the current domain name.
+                String currentDomainName = currentUri.getHost();
+
+                if ((currentDomainName != null) && !currentDomainName.isEmpty()) {
+                    // Get the IP addresses for the current URI.
+                    GetHostIpAddressesCoroutine.getAddresses(currentDomainName, nestedScrollWebView, getSupportFragmentManager(), getString(R.string.pinned_mismatch));
+                }
 
                 // Replace Refresh with Stop if the options menu has been created.  (The first WebView typically begins loading before the menu items are instantiated.)
                 if (optionsMenu != null) {
@@ -5990,8 +5952,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Display the final URL.  Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
                             urlEditText.setText(sanitizedUrl);
 
-                            // Apply text highlighting to the URL.
-                            highlightUrlText();
+                            // Highlight the URL syntax.
+                            UrlHelper.highlightSyntax(urlEditText, initialGrayColorSpan, finalGrayColorSpan, redColorSpan);
                         }
 
                         // Only populate the title text view if the tab has been fully created.
@@ -6060,7 +6022,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
                     } catch (Exception exception) {
                         // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
-                        pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
+                        pendingDialogsArrayList.add(new PendingDialogDataClass(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
                     }
                 }
             }