]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Add a new Share URL menu entry. https://redmine.stoutner.com/issues/838
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index d44636432e2057a9d321fb4facf72b95e9e89fd7..45a1de5b1c94e2c9267c6355eeaa8e9ccf113622 100644 (file)
@@ -1,22 +1,22 @@
 /*
- * Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2022 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
- * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
  *
- * Privacy Browser is free software: you can redistribute it and/or modify
+ * Privacy Browser Android is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
- * Privacy Browser is distributed in the hope that it will be useful,
+ * Privacy Browser Android is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package com.stoutner.privacybrowser.activities;
@@ -79,6 +79,7 @@ import android.webkit.SslErrorHandler;
 import android.webkit.ValueCallback;
 import android.webkit.WebBackForwardList;
 import android.webkit.WebChromeClient;
+import android.webkit.WebResourceRequest;
 import android.webkit.WebResourceResponse;
 import android.webkit.WebSettings;
 import android.webkit.WebStorage;
@@ -117,6 +118,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import androidx.viewpager.widget.ViewPager;
 import androidx.webkit.WebSettingsCompat;
 import androidx.webkit.WebViewFeature;
+import kotlin.Pair;
 
 import com.google.android.material.appbar.AppBarLayout;
 import com.google.android.material.floatingactionbutton.FloatingActionButton;
@@ -203,10 +205,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
     public final static int UNRECOGNIZED_USER_AGENT = -1;
     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
-    public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
+    public final static int SETTINGS_CUSTOM_USER_AGENT = 11;
     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
-    public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
+    public final static int DOMAINS_CUSTOM_USER_AGENT = 12;
 
     // Define the start activity for result request codes.  The public static entry is accessed from `OpenDialog()`.
     private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
@@ -440,7 +442,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                                         Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + fileNameString, Snackbar.LENGTH_SHORT).show();
                                     } catch (Exception exception) {
                                         // Display a snackbar with the exception.
-                                        Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+                                        Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
                                     } finally {
                                         // Delete the temporary MHT file.
                                         //noinspection ResultOfMethodCallIgnored
@@ -453,7 +455,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             });
                         } catch (IOException ioException) {
                             // Display a snackbar with the IO exception.
-                            Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show();
+                            Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + ioException, Snackbar.LENGTH_INDEFINITE).show();
                         }
                     }
                 }
@@ -528,9 +530,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Enable the drawing of the entire webpage.  This makes it possible to save a website image.  This must be done before anything else happens with the WebView.
-        if (Build.VERSION.SDK_INT >= 21) {
-            WebView.enableSlowWholeDocumentDraw();
-        }
+        WebView.enableSlowWholeDocumentDraw();
 
         // Set the theme.
         setTheme(R.style.PrivacyBrowser);
@@ -978,9 +978,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
         optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
 
-        // Only display the dark WebView menu item if API >= 21.
-        optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
-
         // Get the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
@@ -1003,17 +1000,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Set the title.
             optionsRefreshMenuItem.setTitle(R.string.stop);
 
-            // Set the icon if it is displayed in the app bar.
+            // Set the icon if it is displayed in the app bar.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
             if (displayAdditionalAppBarIcons) {
-                // Get the current theme status.
-                int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-                // Set the icon according to the current theme status.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
-                } else {
-                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
-                }
+                optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
             }
         }
 
@@ -1379,12 +1368,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         @Override
                         public void onDismissed(Snackbar snackbar, int event) {
                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
-                                // Delete the cookies, which command varies by SDK.
-                                if (Build.VERSION.SDK_INT < 21) {
-                                    cookieManager.removeAllCookie();
-                                } else {
-                                    cookieManager.removeAllCookies(null);
-                                }
+                                // Delete the cookies.
+                                cookieManager.removeAllCookies(null);
                             }
                         }
                     })
@@ -1811,7 +1796,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             assert printManager != null;
 
             // Create a print document adapter from the current WebView.
-            PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
+            PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(getString(R.string.print));
 
             // Print the document.
             printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
@@ -1865,24 +1850,45 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Consume the event.
             return true;
-        } else if (menuItemId == R.id.share_url) {  // Share URL.
-            // Setup the share string.
+        } else if (menuItemId == R.id.share_message) {  // Share a message.
+            // Prepare the share string.
             String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
 
             // Create the share intent.
-            Intent shareIntent = new Intent(Intent.ACTION_SEND);
+            Intent shareMessageIntent = new Intent(Intent.ACTION_SEND);
 
             // Add the share string to the intent.
-            shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
+            shareMessageIntent.putExtra(Intent.EXTRA_TEXT, shareString);
 
             // Set the MIME type.
-            shareIntent.setType("text/plain");
+            shareMessageIntent.setType("text/plain");
 
             // Set the intent to open in a new task.
-            shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            shareMessageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
             // Make it so.
-            startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
+            startActivity(Intent.createChooser(shareMessageIntent, getString(R.string.share_message)));
+
+            // Consume the event.
+            return true;
+        } else if (menuItemId == R.id.share_url) {  // Share URL.
+            // Create the share intent.
+            Intent shareUrlIntent = new Intent(Intent.ACTION_SEND);
+
+            // Add the URL to the intent.
+            shareUrlIntent.putExtra(Intent.EXTRA_TEXT, currentWebView.getUrl());
+
+            // Add the title to the intent.
+            shareUrlIntent.putExtra(Intent.EXTRA_SUBJECT, currentWebView.getTitle());
+
+            // Set the MIME type.
+            shareUrlIntent.setType("text/plain");
+
+            // Set the intent to open in a new task.
+            shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            //Make it so.
+            startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)));
 
             // Consume the event.
             return true;
@@ -1949,8 +1955,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 Uri currentUri = Uri.parse(currentWebView.getUrl());
                 String currentDomain = currentUri.getHost();
 
-                // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-                DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+                // Initialize the database handler.
+                DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this);
 
                 // Create the domain and store the database ID.
                 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
@@ -2785,11 +2791,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             closeCurrentTab();
         } else {  // There isn't anything to do in Privacy Browser.
             // Close Privacy Browser.  `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
-            if (Build.VERSION.SDK_INT >= 21) {
-                finishAndRemoveTask();
-            } else {
-                finish();
-            }
+            finishAndRemoveTask();
 
             // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
             System.exit(0);
@@ -2805,11 +2807,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Run the commands that correlate to the specified request code.
         switch (requestCode) {
             case BROWSE_FILE_UPLOAD_REQUEST_CODE:
-                // File uploads only work on API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Pass the file to the WebView.
-                    fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
-                }
+                // Pass the file to the WebView.
+                fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
                 break;
 
             case BROWSE_OPEN_REQUEST_CODE:
@@ -2853,21 +2852,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Initialize the formatted URL string.
         String url = "";
 
-        // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
+        // Check to see if the unformatted URL string is a valid URL.  Otherwise, convert it into a search.
         if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
             // Load the entire content URL.
             url = unformattedUrlString;
         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
                 unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
-            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
+            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://")) {
                 unformattedUrlString = "https://" + unformattedUrlString;
             }
 
-            // Initialize `unformattedUrl`.
+            // Initialize the unformatted URL.
             URL unformattedUrl = null;
 
-            // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
+            // Convert the unformatted URL string to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components.
             try {
                 unformattedUrl = new URL(unformattedUrlString);
             } catch (MalformedURLException e) {
@@ -3041,7 +3040,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 currentWebView.loadUrl(temporaryMhtFile.toString());
             } catch (Exception exception) {
                 // Display a snackbar.
-                Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
+                Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
             }
         } else {  // Let the WebView handle opening of the file.
             // Open the file.
@@ -3095,18 +3094,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Remove the lint warning below that the input method manager might be null.
         assert inputMethodManager != null;
 
-        // Initialize the gray foreground color spans for highlighting the URLs.  The deprecated `getResources()` must be used until API >= 23.
-        initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
-        finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+        // Initialize the gray foreground 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(getResources().getColor(R.color.red_a700));
+            redColorSpan = new ForegroundColorSpan(getColor(R.color.red_a700));
         } else {
-            redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
+            redColorSpan = new ForegroundColorSpan(getColor(R.color.red_900));
         }
 
         // Remove the formatting from the URL edit text when the user is editing the text.
@@ -3386,8 +3385,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
 
-        // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
-        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
+        // Initialize the bookmarks database helper.
+        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
 
         // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
         currentBookmarksFolder = "";
@@ -3723,8 +3722,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
             }
 
-            // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
-            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+            // Initialize the database handler.
+            DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this);
 
             // Get a full cursor from `domainsDatabaseHelper`.
             Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
@@ -3735,7 +3734,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Get the domain name column index.
             int domainNameColumnIndex = domainNameCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME);
 
-            // Populate `domainSettingsSet`.
+            // Populate the domain settings set.
             for (int i = 0; i < domainNameCursor.getCount(); i++) {
                 // Move the domains cursor to the current row.
                 domainNameCursor.moveToPosition(i);
@@ -3744,7 +3743,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
             }
 
-            // Close `domainNameCursor.
+            // Close the domain name cursor.
             domainNameCursor.close();
 
             // Initialize the domain name in database variable.
@@ -3763,7 +3762,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
 
             // Check all the subdomains of the host name against wildcard domains in the domain cursor.
-            while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the host name.
+            while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the hostname.
                 if (domainSettingsSet.contains("*." + newHostName)) {  // Check the host name prepended by `*.`.
                     // Set the domain settings applied tracker to true.
                     nestedScrollWebView.setDomainSettingsApplied(true);
@@ -3799,12 +3798,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
 
             if (nestedScrollWebView.getDomainSettingsApplied()) {  // The url has custom domain settings.
-                // Get a cursor for the current host and move it to the first position.
+                // Remove the incorrect lint warning below that the domain name in database might be null.
+                assert domainNameInDatabase != null;
+
+                // Get a cursor for the current host.
                 Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
+
+                // Move to the first position.
                 currentDomainSettingsCursor.moveToFirst();
 
                 // Get the settings from the cursor.
-                nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper._ID)));
+                nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID)));
                 nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
                 nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)) == 1);
                 nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
@@ -4018,15 +4022,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         break;
                 }
 
-                // Get the current theme status.
-                int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
-                } else {
-                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
-                }
+                urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null));
             } else {  // The new URL does not have custom domain settings.  Load the defaults.
                 // Store the values from the shared preferences.
                 nestedScrollWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
@@ -4307,35 +4304,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode);
             }
 
-            // Get the current theme status.
-            int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
             // Update the cookies icon.
-            if (currentWebView.getAcceptCookies()) {  // Cookies are enabled.
+            if (currentWebView.getAcceptCookies()) {
                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
-            } else {  // Cookies are disabled.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
-                } else {
-                    optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
-                }
+            } else {
+                optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled);
             }
 
             // Update the refresh icon.
             if (optionsRefreshMenuItem.getTitle() == getString(R.string.refresh)) {  // The refresh icon is displayed.
-                // Set the icon according to the theme.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
-                } else {
-                    optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
-                }
+                // Set the icon.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
+                optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled);
             } else {  // The stop icon is displayed.
-                // Set the icon according to the theme.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
-                } else {
-                    optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
-                }
+                // Set the icon.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
+                optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
             }
 
             // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar.
@@ -4778,12 +4760,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Clear cookies.
         if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
-            // The command to remove cookies changed slightly in API 21.
-            if (Build.VERSION.SDK_INT >= 21) {
-                CookieManager.getInstance().removeAllCookies(null);
-            } else {
-                CookieManager.getInstance().removeAllCookie();
-            }
+            // Request the cookies be deleted.
+            CookieManager.getInstance().removeAllCookies(null);
 
             // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
             try {
@@ -4941,11 +4919,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
-        if (Build.VERSION.SDK_INT >= 21) {
-            finishAndRemoveTask();
-        } else {
-            finish();
-        }
+        finishAndRemoveTask();
 
         // Remove the terminated program from RAM.  The status code is `0`.
         System.exit(0);
@@ -5037,16 +5011,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Set the background to indicate the domain settings status.
             if (currentWebView.getDomainSettingsApplied()) {
-                // Get the current theme status.
-                int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-                // Set a green background on the URL relative layout to indicate that custom domain settings are being used.
-                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
-                } else {
-                    urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
-                }
+                // Set a background on the URL relative layout to indicate that custom domain settings are being used.
+                urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null));
             } else {
+                // Remove any background on the URL relative layout.
                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
             }
         } else {  // The fragment has not been populated.  Try again in 100 milliseconds.
@@ -5130,9 +5098,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         nestedScrollWebView.getSettings().setDisplayZoomControls(false);
 
         // Don't allow mixed content (HTTP and HTTPS) on the same website.
-        if (Build.VERSION.SDK_INT >= 21) {
-            nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
-        }
+        nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
 
         // Set the WebView to load in overview mode (zoomed out to the maximum width).
         nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
@@ -5304,72 +5270,46 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         });
 
         // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.  Also reinforce full screen browsing mode.
-        // On API < 23, `getViewTreeObserver().addOnScrollChangedListener()` must be used, but it is a little bit buggy and appears to get garbage collected from time to time.
-        if (Build.VERSION.SDK_INT >= 23) {
-            nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
-                // Set the swipe to refresh status.
-                if (nestedScrollWebView.getSwipeToRefresh()) {
-                    // Only enable swipe to refresh if the WebView is scrolled to the top.
-                    swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
-                } else {
-                    // Disable swipe to refresh.
-                    swipeRefreshLayout.setEnabled(false);
-                }
-
-                //  Scroll the bottom app bar if enabled.
-                if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) {
-                    if (scrollY < oldScrollY) {  // The WebView was scrolled down.
-                        // Animate the bottom app bar onto the screen.
-                        objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
+        nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            // Set the swipe to refresh status.
+            if (nestedScrollWebView.getSwipeToRefresh()) {
+                // Only enable swipe to refresh if the WebView is scrolled to the top.
+                swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
+            } else {
+                // Disable swipe to refresh.
+                swipeRefreshLayout.setEnabled(false);
+            }
 
-                        // Make it so.
-                        objectAnimator.start();
-                    } else if (scrollY > oldScrollY) {  // The WebView was scrolled up.
-                        // Animate the bottom app bar off the screen.
-                        objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight());
+            //  Scroll the bottom app bar if enabled.
+            if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) {
+                if (scrollY < oldScrollY) {  // The WebView was scrolled down.
+                    // Animate the bottom app bar onto the screen.
+                    objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
 
-                        // Make it so.
-                        objectAnimator.start();
-                    }
-                }
+                    // Make it so.
+                    objectAnimator.start();
+                } else if (scrollY > oldScrollY) {  // The WebView was scrolled up.
+                    // Animate the bottom app bar off the screen.
+                    objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight());
 
-                // Reinforce the system UI visibility flags if in full screen browsing mode.
-                // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
-                if (inFullScreenBrowsingMode) {
-                    /* Hide the system bars.
-                     * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                     * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                     * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                     * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                     */
-                    rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-                }
-            });
-        } else {
-            nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
-                if (nestedScrollWebView.getSwipeToRefresh()) {
-                    // Only enable swipe to refresh if the WebView is scrolled to the top.
-                    swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
-                } else {
-                    // Disable swipe to refresh.
-                    swipeRefreshLayout.setEnabled(false);
+                    // Make it so.
+                    objectAnimator.start();
                 }
+            }
 
-                // Reinforce the system UI visibility flags if in full screen browsing mode.
-                // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
-                if (inFullScreenBrowsingMode) {
-                    /* Hide the system bars.
-                     * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                     * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                     * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                     * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                     */
-                    rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-                }
-            });
-        }
+            // Reinforce the system UI visibility flags if in full screen browsing mode.
+            // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
+            if (inFullScreenBrowsingMode) {
+                /* Hide the system bars.
+                 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                 */
+                rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+            }
+        });
 
         // Set the web chrome client.
         nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
@@ -5501,34 +5441,31 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Upload files.
             @Override
             public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
-                // Show the file chooser if the device is running API >= 21.
-                if (Build.VERSION.SDK_INT >= 21) {
-                    // Store the file path callback.
-                    fileChooserCallback = filePathCallback;
+                // Store the file path callback.
+                fileChooserCallback = filePathCallback;
 
-                    // Create an intent to open a chooser based on the file chooser parameters.
-                    Intent fileChooserIntent = fileChooserParams.createIntent();
+                // Create an intent to open a chooser based on the file chooser parameters.
+                Intent fileChooserIntent = fileChooserParams.createIntent();
 
-                    // Get a handle for the package manager.
-                    PackageManager packageManager = getPackageManager();
+                // Get a handle for the package manager.
+                PackageManager packageManager = getPackageManager();
 
-                    // Check to see if the file chooser intent resolves to an installed package.
-                    if (fileChooserIntent.resolveActivity(packageManager) != null) {  // The file chooser intent is fine.
-                        // Start the file chooser intent.
-                        startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
-                    } else {  // The file chooser intent will cause a crash.
-                        // Create a generic intent to open a chooser.
-                        Intent genericFileChooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
+                // Check to see if the file chooser intent resolves to an installed package.
+                if (fileChooserIntent.resolveActivity(packageManager) != null) {  // The file chooser intent is fine.
+                    // Start the file chooser intent.
+                    startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
+                } else {  // The file chooser intent will cause a crash.
+                    // Create a generic intent to open a chooser.
+                    Intent genericFileChooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
 
-                        // Request an openable file.
-                        genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE);
+                    // Request an openable file.
+                    genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE);
 
-                        // Set the file type to everything.
-                        genericFileChooserIntent.setType("*/*");
+                    // Set the file type to everything.
+                    genericFileChooserIntent.setType("*/*");
 
-                        // Start the generic file chooser intent.
-                        startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
-                    }
+                    // Start the generic file chooser intent.
+                    startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
                 }
                 return true;
             }
@@ -5618,7 +5555,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
             @Override
-            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+            public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest webResourceRequest) {
+                // Get the URL.
+                String url = webResourceRequest.getUrl().toString();
+
                 // Check to see if the resource request is for the main URL.
                 if (url.equals(nestedScrollWebView.getCurrentUrl())) {
                     // `return null` loads the resource request, which should never be blocked if it is the main URL.
@@ -5638,9 +5578,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     }
                 }
 
-                // Sanitize the URL.
-                url = sanitizeUrl(url);
-
                 // Create an empty web resource response to be used if the resource request is blocked.
                 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
 
@@ -5656,31 +5593,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Store a copy of the current domain for use in later requests.
                 String currentDomain = currentBaseDomain;
 
-                // Nobody is happy when comparing null strings.
-                if (url != null) {
-                    // Convert the request URL to a URI.
-                    Uri requestUri = Uri.parse(url);
-
-                    // Get the request host name.
-                    String requestBaseDomain = requestUri.getHost();
+                // Get the request host name.
+                String requestBaseDomain = webResourceRequest.getUrl().getHost();
 
-                    // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
-                    if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
-                        // Determine the current base domain.
-                        while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
-                            // Remove the first subdomain.
-                            currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
-                        }
-
-                        // Determine the request base domain.
-                        while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
-                            // Remove the first subdomain.
-                            requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
-                        }
+                // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
+                if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
+                    // Determine the current base domain.
+                    while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
+                        // Remove the first subdomain.
+                        currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
+                    }
 
-                        // Update the third party request tracker.
-                        isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
+                    // Determine the request base domain.
+                    while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
+                        // Remove the first subdomain.
+                        requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
                     }
+
+                    // Update the third party request tracker.
+                    isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
                 }
 
                 // Get the current WebView page position.
@@ -6037,17 +5968,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Get the app bar and theme preferences.
                     boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
 
-                    // If the icon is displayed in the AppBar, set it according to the theme.
+                    // 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) {
-                        // Get the current theme status.
-                        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-                        // Set the stop icon according to the theme.
-                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                            optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
-                        } else {
-                            optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
-                        }
+                        optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
                     }
                 }
             }
@@ -6055,7 +5978,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             @Override
             public void onPageFinished(WebView view, String url) {
                 // Flush any cookies to persistent storage.  The cookie manager has become very lazy about flushing cookies in recent versions.
-                if (nestedScrollWebView.getAcceptCookies() && Build.VERSION.SDK_INT >= 21) {
+                if (nestedScrollWebView.getAcceptCookies()) {
                     CookieManager.getInstance().flush();
                 }
 
@@ -6069,15 +5992,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     // If the icon is displayed in the app bar, reset it according to the theme.
                     if (displayAdditionalAppBarIcons) {
-                        // Get the current theme status.
-                        int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
-
-                        // Set the icon according to the theme.
-                        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
-                            optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
-                        } else {
-                            optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
-                        }
+                        // Set the icon.
+                        optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled);
                     }
                 }
 
@@ -6186,7 +6102,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
             }
 
-            // Handle SSL Certificate errors.
+            // Handle SSL Certificate errors.  Suppress the lint warning that ignoring the error might be dangerous.
+            @SuppressLint("WebViewClientOnReceivedSslError")
             @Override
             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                 // Get the current website SSL certificate.
@@ -6205,11 +6122,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
                 if (nestedScrollWebView.hasPinnedSslCertificate()) {
                     // Get the pinned SSL certificate.
-                    ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
+                    Pair<String[], Date[]> pinnedSslCertificatePair = nestedScrollWebView.getPinnedSslCertificate();
 
                     // Extract the arrays from the array list.
-                    String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
-                    Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
+                    String[] pinnedSslCertificateStringArray = pinnedSslCertificatePair.getFirst();
+                    Date[] pinnedSslCertificateDateArray = pinnedSslCertificatePair.getSecond();
 
                     // Check if the current SSL certificate matches the pinned certificate.
                     if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&