]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Add swipe to refresh to domain and on-the-fly settings. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index ff82699621388e50c22ab34879fef9c3c673369e..02b7025580a541e409dead26d4e87cf3fc64157e 100644 (file)
@@ -25,6 +25,7 @@ import android.Manifest;
 import android.annotation.SuppressLint;
 import android.app.DialogFragment;
 import android.app.DownloadManager;
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
 import android.content.ClipboardManager;
@@ -87,6 +88,7 @@ import android.view.inputmethod.InputMethodManager;
 import android.webkit.CookieManager;
 import android.webkit.HttpAuthHandler;
 import android.webkit.SslErrorHandler;
+import android.webkit.ValueCallback;
 import android.webkit.WebBackForwardList;
 import android.webkit.WebChromeClient;
 import android.webkit.WebResourceResponse;
@@ -94,6 +96,7 @@ import android.webkit.WebStorage;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.webkit.WebViewDatabase;
+import android.widget.ArrayAdapter;
 import android.widget.CursorAdapter;
 import android.widget.EditText;
 import android.widget.FrameLayout;
@@ -105,10 +108,9 @@ import android.widget.RadioButton;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
-import com.stoutner.privacybrowser.BannerAd;
 import com.stoutner.privacybrowser.BuildConfig;
 import com.stoutner.privacybrowser.R;
-import com.stoutner.privacybrowser.dialogs.AddDomainDialog;
+import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
@@ -120,6 +122,7 @@ import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
 import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
+import com.stoutner.privacybrowser.helpers.AdHelper;
 import com.stoutner.privacybrowser.helpers.BlockListHelper;
 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
@@ -145,11 +148,11 @@ import java.util.Map;
 import java.util.Set;
 
 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
-public class MainWebViewActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener, CreateBookmarkDialog.CreateBookmarkListener,
-        CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, DownloadFileDialog.DownloadFileListener,
-        DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
-        EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener,
-        PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
+public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
+        CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
+        DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
+        HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener,
+        SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
 
     // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`,
     // `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`,
@@ -182,7 +185,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`
     public static boolean reloadOnRestart;
 
-    // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`.
+    // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`.  It is also used in `onRestart()`.
     public static boolean loadUrlOnRestart;
 
     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
@@ -211,6 +214,14 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     public static Date pinnedDomainSslStartDate;
     public static Date pinnedDomainSslEndDate;
 
+    // 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 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;
+
 
     // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applyAppSettings()`.
     private ActionBar appBar;
@@ -234,7 +245,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
     private FrameLayout fullScreenVideoFrameLayout;
 
-    // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
+    // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsMenuSelected()`, and `onRestart()`.
     private SwipeRefreshLayout swipeRefreshLayout;
 
     // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
@@ -267,9 +278,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     // `nightMode` is used in `onCreate()` and  `applyDomainSettings()`.
     private boolean nightMode;
 
-    // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applyAppSettings()`.
-    private boolean swipeToRefreshEnabled;
-
     // `displayWebpageImagesBoolean` is used in `applyAppSettings()` and `applyDomainSettings()`.
     private boolean displayWebpageImagesBoolean;
 
@@ -282,12 +290,15 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     // The block list variables are used in `onCreate()` and `applyAppSettings()`.
     private boolean easyListEnabled;
     private boolean easyPrivacyEnabled;
-    private boolean fanboyAnnoyanceListEnabled;
-    private boolean fanboySocialBlockingListEnabled;
+    private boolean fanboysAnnoyanceListEnabled;
+    private boolean fanboysSocialBlockingListEnabled;
 
     // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
     private Runtime privacyBrowserRuntime;
 
+    // `proxyThroughOrbot` is used in `onRestart()` and `applyAppSettings()`.
+    private boolean proxyThroughOrbot;
+
     // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
     private boolean incognitoModeEnabled;
 
@@ -356,9 +367,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     private ForegroundColorSpan initialGrayColorSpan;
     private ForegroundColorSpan finalGrayColorSpan;
 
-    // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
-    private View adView;
-
     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
     private SslErrorHandler sslErrorHandler;
 
@@ -395,6 +403,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
     private String oldFolderNameString;
 
+    // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
+    private ValueCallback<Uri[]> fileChooserCallback;
+
     // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
     private String downloadUrl;
     private String downloadContentDisposition;
@@ -403,6 +414,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
     private String downloadImageUrl;
 
+    // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
+    private ArrayAdapter<CharSequence> userAgentNamesArray;
+    private String[] userAgentDataArray;
+
     // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
     private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
@@ -557,14 +572,14 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
             startActivity(bookmarksIntent);
         });
 
-        // Set the create new bookmark folder FAB to display the `AlertDialog`.
+        // Set the create new bookmark folder FAB to display an alert dialog.
         createBookmarkFolderFab.setOnClickListener(v -> {
             // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
             AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
             createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder));
         });
 
-        // Set the create new bookmark FAB to display the `AlertDialog`.
+        // Set the create new bookmark FAB to display an alert dialog.
         createBookmarkFab.setOnClickListener(view -> {
             // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
             AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
@@ -584,9 +599,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                         // Hide the `appBar`.
                         appBar.hide();
 
-                        // Hide the `BannerAd` in the free flavor.
+                        // Hide the banner ad in the free flavor.
                         if (BuildConfig.FLAVOR.contentEquals("free")) {
-                            BannerAd.hideAd(adView);
+                            // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+                            AdHelper.hideAd(findViewById(R.id.adview));
                         }
 
                         // Modify the system bars.
@@ -621,11 +637,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
                         // Show the `BannerAd` in the free flavor.
                         if (BuildConfig.FLAVOR.contentEquals("free")) {
-                            // Reload the ad.  Because the screen may have rotated, we need to use `reloadAfterRotate`.
-                            BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
-
-                            // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
-                            adView = findViewById(R.id.adview);
+                            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+                            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id));
                         }
 
                         // Remove the translucent navigation bar flag if it is set.
@@ -747,7 +760,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
             // Convert the id from long to int to match the format of the bookmarks database.
             int databaseID = (int) id;
 
-            // Get the bookmark `Cursor` for this ID and move it to the first row.
+            // Get the bookmark cursor for this ID and move it to the first row.
             Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
             bookmarkCursor.moveToFirst();
 
@@ -878,7 +891,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                         mainWebView.setVisibility(View.VISIBLE);
                     }
 
-                    //Stop the `SwipeToRefresh` indicator if it is running
+                    //Stop the swipe to refresh indicator if it is running
                     swipeRefreshLayout.setRefreshing(false);
                 }
             }
@@ -903,12 +916,13 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 webViewTitle = title;
             }
 
-            // Enter full screen video
+            // Enter full screen video.
             @Override
             public void onShowCustomView(View view, CustomViewCallback callback) {
                 // Pause the ad if this is the free flavor.
                 if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    BannerAd.pauseAd(adView);
+                    // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+                    AdHelper.pauseAd(findViewById(R.id.adview));
                 }
 
                 // Remove the translucent overlays.
@@ -931,7 +945,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
             }
 
-            // Exit full screen video
+            // Exit full screen video.
+            @Override
             public void onHideCustomView() {
                 // Hide `fullScreenVideoFrameLayout`.
                 fullScreenVideoFrameLayout.removeAllViews();
@@ -945,12 +960,26 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
                 // Show the ad if this is the free flavor.
                 if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    // Reload the ad.  Because the screen may have rotated, we need to use `reloadAfterRotate`.
-                    BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
+                    // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+                    AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id));
+                }
+            }
 
-                    // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
-                    adView = findViewById(R.id.adview);
+            // 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;
+
+                    // Create an intent to open a chooser based ont the file chooser parameters.
+                    Intent fileChooserIntent = fileChooserParams.createIntent();
+
+                    // Open the file chooser.  Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
+                    startActivityForResult(fileChooserIntent, 0);
                 }
+                return true;
             }
         });
 
@@ -1033,9 +1062,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
         inFullScreenBrowsingMode = false;
 
-        // Initialize AdView for the free flavor.
-        adView = findViewById(R.id.adview);
-
         // Initialize the privacy settings variables.
         javaScriptEnabled = false;
         firstPartyCookiesEnabled = false;
@@ -1044,10 +1070,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         saveFormDataEnabled = false;
         nightMode = false;
 
-        // Initialize `webViewTitle`.
+        // Initialize the WebView title.
         webViewTitle = getString(R.string.no_title);
 
-        // Initialize `favoriteIconBitmap`.  `ContextCompat` must be used until API >= 21.
+        // Initialize the favorite icon bitmap.  `ContextCompat` must be used until API >= 21.
         Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
         BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
         assert favoriteIconBitmapDrawable != null;
@@ -1058,6 +1084,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
             favoriteIconBitmap = favoriteIconDefaultBitmap;
         }
 
+        // Initialize the user agent array adapter and string array.
+        userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.domain_settings_spinner_item);
+        userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
+
         // Apply the app settings from the shared preferences.
         applyAppSettings();
 
@@ -1078,46 +1108,68 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
         mainWebView.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.
-            // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
+            // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
             @SuppressWarnings("deprecation")
             @Override
             public boolean shouldOverrideUrlLoading(WebView view, String url) {
-                if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
+                if (url.startsWith("http")) {  // Load the URL in Privacy Browser.
+                    // Apply the domain settings for the new URL.
+                    applyDomainSettings(url, true, false);
+
+                    // Returning false causes the current `WebView` to handle the URL and prevents it from adding redirects to the history list.
+                    return false;
+                } else if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
 
-                    // Parse the url and set it as the data for the `Intent`.
+                    // Parse the url and set it as the data for the intent.
                     emailIntent.setData(Uri.parse(url));
 
-                    // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
+                    // Open the email program in a new task instead of as part of Privacy Browser.
                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
                     // Make it so.
                     startActivity(emailIntent);
 
-                    // Returning `true` indicates the application is handling the URL.
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
                     return true;
                 } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
-                    // `ACTION_DIAL` open the dialer and loads the phone number, but waits for the user to place the call.
+                    // Open the dialer and load the phone number, but wait for the user to place the call.
                     Intent dialIntent = new Intent(Intent.ACTION_DIAL);
 
                     // Add the phone number to the intent.
                     dialIntent.setData(Uri.parse(url));
 
-                    // `FLAG_ACTIVITY_NEW_TASK` opens the dialer in a new task instead as part of Privacy Browser.
+                    // Open the dialer in a new task instead of as part of Privacy Browser.
                     dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
                     // Make it so.
                     startActivity(dialIntent);
 
-                    // Returning `true` indicates the application is handling the URL.
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
                     return true;
-                } else {  // Load the URL in Privacy Browser.
-                    // Apply the domain settings for the new URL.
-                    applyDomainSettings(url, true);
+                } else {  // Load a system chooser to select an app that can handle the URL.
+                    // Open an app that can handle the URL.
+                    Intent genericIntent = new Intent(Intent.ACTION_VIEW);
 
-                    // Returning `false` causes the current `WebView` to handle the URL and prevents it from adding redirects to the history list.
-                    return false;
+                    // Add the URL to the intent.
+                    genericIntent.setData(Uri.parse(url));
+
+                    // List all apps that can handle the URL instead of just opening the first one.
+                    genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
+
+                    // Open the app in a new task instead of as part of Privacy Browser.
+                    genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                    // Start the app or display a snackbar if no app is available to handle the URL.
+                    try {
+                        startActivity(genericIntent);
+                    } catch (ActivityNotFoundException exception) {
+                        Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + "  " + url, Snackbar.LENGTH_SHORT).show();
+                    }
+
+                    // Returning true indicates Privacy Browser is handling the URL by creating an intent.
+                    return true;
                 }
             }
 
@@ -1145,12 +1197,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 }
 
                 // Check Fanboy’s Annoyance List if it is enabled.
-                if (fanboyAnnoyanceListEnabled) {
+                if (fanboysAnnoyanceListEnabled) {
                     if (blockListHelper.isBlocked(formattedUrlString, url, fanboyAnnoyance)) {
                         // The resource request was blocked.  Return an empty web resource response.
                         return emptyWebResourceResponse;
                     }
-                } else if (fanboySocialBlockingListEnabled){  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
+                } else if (fanboysSocialBlockingListEnabled){  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
                     if (blockListHelper.isBlocked(formattedUrlString, url, fanboySocial)) {
                         // The resource request was blocked.  Return an empty web resource response.
                         return emptyWebResourceResponse;
@@ -1195,7 +1247,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
                     // Apply any custom domain settings if the URL was loaded by navigating history.
                     if (navigatingHistory) {
-                        applyDomainSettings(url, true);
+                        applyDomainSettings(url, true, false);
                     }
 
                     // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
@@ -1224,11 +1276,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
                     // Manually delete cache folders.
                     try {
-                        // Delete the main `cache` folder.
+                        // Delete the main cache directory.
                         privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
 
-                        // Delete the `app_webview` folder, which contains an additional `WebView` cache.  See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
-                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
+                        // Delete the secondary `Service Worker` cache directory.
+                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+                        privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
                     } catch (IOException e) {
                         // Do nothing if an error is thrown.
                     }
@@ -1250,7 +1303,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                         inputMethodManager.showSoftInput(urlTextBox, 0);
 
                         // Apply the domain settings.  This clears any settings from the previous domain.
-                        applyDomainSettings(formattedUrlString, true);
+                        applyDomainSettings(formattedUrlString, true, false);
                     } else {  // `WebView` has loaded a webpage.
                         // Set `formattedUrlString`.
                         formattedUrlString = url;
@@ -1406,6 +1459,18 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         // Run the default commands.
         super.onRestart();
 
+        // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
+        if (proxyThroughOrbot) {
+            // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
+            Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
+
+            // Send the intent to the Orbot package.
+            orbotIntent.setPackage("org.torproject.android");
+
+            // Make it so.
+            sendBroadcast(orbotIntent);
+        }
+
         // Apply the app settings if returning from the Settings activity..
         if (reapplyAppSettingsOnRestart) {
             // Apply the app settings.
@@ -1427,7 +1492,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         // Apply the domain settings if returning from the Domains activity.
         if (reapplyDomainSettingsOnRestart) {
             // Reapply the domain settings.
-            applyDomainSettings(formattedUrlString, false);
+            applyDomainSettings(formattedUrlString, false, true);
 
             // Reset `reapplyDomainSettingsOnRestart`.
             reapplyDomainSettingsOnRestart = false;
@@ -1472,7 +1537,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
         // Resume the adView for the free flavor.
         if (BuildConfig.FLAVOR.contentEquals("free")) {
-            BannerAd.resumeAd(adView);
+            // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+            AdHelper.resumeAd(findViewById(R.id.adview));
         }
     }
 
@@ -1486,7 +1552,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
         // Pause the adView or it will continue to consume resources in the background on the free flavor.
         if (BuildConfig.FLAVOR.contentEquals("free")) {
-            BannerAd.pauseAd(adView);
+            // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+            AdHelper.pauseAd(findViewById(R.id.adview));
         }
 
         super.onPause();
@@ -1542,8 +1609,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);
         MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
+        MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
         MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
-        MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
+        MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
 
         // Set the text for the domain menu item.
         if (domainSettingsApplied) {
@@ -1557,6 +1625,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
         toggleDomStorageMenuItem.setChecked(domStorageEnabled);
         toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled);
+        swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
         displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
 
         // Enable third-party cookies if first-party cookies are enabled.
@@ -1649,8 +1718,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         fontSizeMenuItem.setTitle(fontSizeTitle);
         selectedFontSizeMenuItem.setChecked(true);
 
-        // Only show `Refresh` if `swipeToRefresh` is disabled.
-        refreshMenuItem.setVisible(!swipeToRefreshEnabled);
+        // Only show Ad Consent if this is the free flavor.
+        adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
 
         // Run all the other default commands.
         super.onPrepareOptionsMenu(menu);
@@ -1670,27 +1739,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
         // Set the commands that relate to the menu entries.
         switch (menuItemId) {
-            case R.id.add_or_edit_domain:
-                if (domainSettingsApplied) {  // Edit the current domain settings.
-                    // Reapply the domain settings on returning to `MainWebViewActivity`.
-                    reapplyDomainSettingsOnRestart = true;
-                    currentDomainName = "";
-
-                    // Create an intent to launch the domains activity.
-                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
-
-                    // Put extra information instructing the domains activity to directly load the current domain.
-                    domainsIntent.putExtra("LoadDomain", domainSettingsDatabaseId);
-
-                    // Make it so.
-                    startActivity(domainsIntent);
-                } else {  // Add a new domain.
-                    // Show the add domain `AlertDialog`.
-                    AppCompatDialogFragment addDomainDialog = new AddDomainDialog();
-                    addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain));
-                }
-                return true;
-
             case R.id.toggle_javascript:
                 // Switch the status of javaScriptEnabled.
                 javaScriptEnabled = !javaScriptEnabled;
@@ -1714,6 +1762,48 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 mainWebView.reload();
                 return true;
 
+            case R.id.add_or_edit_domain:
+                if (domainSettingsApplied) {  // Edit the current domain settings.
+                    // Reapply the domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
+                    currentDomainName = "";
+
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+                    // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
+                    domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
+                    domainsIntent.putExtra("closeOnBack", true);
+
+                    // Make it so.
+                    startActivity(domainsIntent);
+                } else {  // Add a new domain.
+                    // Apply the new domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
+                    currentDomainName = "";
+
+                    // Get the current domain
+                    Uri currentUri = Uri.parse(formattedUrlString);
+                    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);
+
+                    // Create the domain and store the database ID.
+                    int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
+
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+                    // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
+                    domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
+                    domainsIntent.putExtra("closeOnBack", true);
+
+                    // Make it so.
+                    startActivity(domainsIntent);
+                }
+                return true;
+
             case R.id.toggle_first_party_cookies:
                 // Switch the status of firstPartyCookiesEnabled.
                 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
@@ -1730,7 +1820,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 // Display a `Snackbar`.
                 if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
                     Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
-                } else if (javaScriptEnabled){  // JavaScript is still enabled.
+                } else if (javaScriptEnabled) {  // JavaScript is still enabled.
                     Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
                 } else {  // Privacy mode.
                     Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
@@ -1936,6 +2026,11 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 mainWebView.getSettings().setTextZoom(200);
                 return true;
 
+            case R.id.swipe_to_refresh:
+                // Toggle swipe to refresh.
+                swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
+                return true;
+
             case R.id.display_images:
                 if (mainWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
                     mainWebView.getSettings().setLoadsImagesAutomatically(false);
@@ -1948,6 +2043,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 onTheFlyDisplayImagesSet = true;
                 return true;
 
+            case R.id.view_source:
+                // Launch the View Source activity.
+                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
+                startActivity(viewSourceIntent);
+                return true;
+
             case R.id.share:
                 // Setup the share string.
                 String shareString = webViewTitle + " â€“ " + urlTextBox.getText().toString();
@@ -1994,12 +2095,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
                 return true;
 
-            case R.id.view_source:
-                // Launch the Vew Source activity.
-                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
-                startActivity(viewSourceIntent);
-                return true;
-
             case R.id.add_to_homescreen:
                 // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
                 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
@@ -2012,6 +2107,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 mainWebView.reload();
                 return true;
 
+            case R.id.ad_consent:
+                // Display the ad consent dialog.
+                DialogFragment adConsentDialogFragment = new AdConsentDialog();
+                adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent));
+                return true;
+
             default:
                 // Don't consume the event.
                 return super.onOptionsItemSelected(menuItem);
@@ -2173,8 +2274,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                     try {
                         // Delete the main cache directory.
                         privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
+
                         // Delete the secondary `Service Worker` cache directory.
-                        // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
                         privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
                     } catch (IOException e) {
                         // Do nothing if an error is thrown.
@@ -2240,11 +2342,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
         // Reload the ad for the free flavor if we are not in full screen mode.
         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-            // Reload the ad.
-            BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
-
-            // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
-            adView = findViewById(R.id.adview);
+            // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+            AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id));
         }
 
         // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
@@ -2276,14 +2375,14 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 // Set the target URL as the title of the `ContextMenu`.
                 menu.setHeaderTitle(linkUrl);
 
-                // Add a `Load URL` entry.
-                menu.add(R.string.load_url).setOnMenuItemClickListener(item -> {
+                // Add a Load URL entry.
+                menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> {
                     loadUrl(linkUrl);
                     return false;
                 });
 
-                // Add a `Copy URL` entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+                // Add a Copy URL entry.
+                menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
                     // Save the link URL in a `ClipData`.
                     ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
 
@@ -2292,6 +2391,38 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                     return false;
                 });
 
+                // Add a Download URL entry.
+                menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
+                    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
+                        // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
+
+                        // Store the variables for future use by `onRequestPermissionsResult()`.
+                        downloadUrl = linkUrl;
+                        downloadContentDisposition = "none";
+                        downloadContentLength = -1;
+
+                        // Show a dialog if the user has previously denied the permission.
+                        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
+                            // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
+                            DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
+
+                            // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
+                            downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+                        } else {  // Show the permission request directly.
+                            // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
+                            ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
+                        }
+                    } else {  // The WRITE_EXTERNAL_STORAGE permission has already been granted.
+                        // Get a handle for the download file alert dialog.
+                        AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
+
+                        // Show the download file alert dialog.
+                        downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                    }
+                    return false;
+                });
+
                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
                 menu.add(R.string.cancel);
                 break;
@@ -2452,33 +2583,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         }
     }
 
-    @Override
-    public void onAddDomain(AppCompatDialogFragment dialogFragment) {
-        // Reapply the domain settings on returning to `MainWebViewActivity`.
-        reapplyDomainSettingsOnRestart = true;
-        currentDomainName = "";
-
-        // Get the new domain name `String` from `dialogFragment`.
-        EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
-        String domainNameString = domainNameEditText.getText().toString();
-
-        // Initialize the database handler.  `this` specifies the context.  The two `nulls` do not specify the database name or a `CursorFactory`.
-        // 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);
-
-        // Create the domain and store the database ID in `currentDomainDatabaseId`.
-        int newDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString);
-
-        // Create an intent to launch the domains activity.
-        Intent domainsIntent = new Intent(this, DomainsActivity.class);
-
-        // Put extra information instructing the domains activity to directly load the current domain.
-        domainsIntent.putExtra("LoadDomain", newDomainDatabaseId);
-
-        // Make it so.
-        startActivity(domainsIntent);
-    }
-
     @Override
     public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
         // Get the `EditTexts` from the `dialogFragment`.
@@ -2927,6 +3031,16 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         }
     }
 
+    // Process the results of an upload file chooser.  Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // 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, data));
+        }
+    }
+
     private void loadUrlFromTextBox() throws UnsupportedEncodingException {
         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
         String unformattedUrlString = urlTextBox.getText().toString().trim();
@@ -2978,7 +3092,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
     private void loadUrl(String url) {
         // Apply any custom domain settings.
-        applyDomainSettings(url, true);
+        applyDomainSettings(url, true, false);
 
         // Load the URL.
         mainWebView.loadUrl(url, customHeaders);
@@ -3025,17 +3139,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         String torSearchCustomURLString = sharedPreferences.getString("tor_search_custom_url", "");
         String searchString = sharedPreferences.getString("search", "https://duckduckgo.com/html/?q=");
         String searchCustomURLString = sharedPreferences.getString("search_custom_url", "");
-        easyListEnabled = sharedPreferences.getBoolean("easylist", true);
-        easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
-        fanboyAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboy_annoyance_list", true);
-        fanboySocialBlockingListEnabled = sharedPreferences.getBoolean("fanboy_social_blocking_list", true);
         incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
         boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
-        boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
+        proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
         hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
         translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
-        swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh", false);
         displayWebpageImagesBoolean = sharedPreferences.getBoolean("display_webpage_images", true);
 
         // Set the homepage, search, and proxy options.
@@ -3103,9 +3212,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
             waitingForOrbot = false;
         }
 
-        // Set swipe to refresh.
-        swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
-
         // Set Do Not Track status.
         if (doNotTrackEnabled) {
             customHeaders.put("DNT", "1");
@@ -3153,11 +3259,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
             // Show the `BannerAd` in the free flavor.
             if (BuildConfig.FLAVOR.contentEquals("free")) {
-                // Reload the ad.  Because the screen may have rotated, we need to use `reloadAfterRotate`.
-                BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
-
-                // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
-                adView = findViewById(R.id.adview);
+                // Initialize the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+                AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.ad_id));
             }
 
             // Remove the translucent navigation bar flag if it is set.
@@ -3174,10 +3277,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
         }
     }
 
-    //
+    // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
     // The deprecated `.getDrawable()` must be used until the minimum API >= 21.
     @SuppressWarnings("deprecation")
-    private void applyDomainSettings(String url, boolean resetFavoriteIcon) {
+    private void applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
         // Reset `navigatingHistory`.
         navigatingHistory = false;
 
@@ -3266,8 +3369,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
 
             // Store the general preference information.
             String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
-            String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
+            String defaultUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser");
             String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
+            boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
             nightMode = sharedPreferences.getBoolean("night_mode", false);
 
             if (domainSettingsApplied) {  // The url we are loading has custom domain settings.
@@ -3282,10 +3386,15 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
                 domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
                 saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
-                String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
+                easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
+                easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
+                fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
+                fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
+                String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
                 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
-                displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+                int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
                 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
+                displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
                 pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
                 pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
                 pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
@@ -3348,37 +3457,70 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
                 // <https://redmine.stoutner.com/issues/160>
                 if (!urlIsLoading) {
-                    switch (userAgentString) {
-                        case "System default user agent":
-                            // Set the user agent according to the system default.
-                            switch (defaultUserAgentString) {
-                                case "WebView default user agent":
-                                    // Set the user agent to `""`, which uses the default value.
-                                    mainWebView.getSettings().setUserAgentString("");
-                                    break;
-
-                                case "Custom user agent":
-                                    // Set the custom user agent.
-                                    mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
-                                    break;
-
-                                default:
-                                    // Use the selected user agent.
-                                    mainWebView.getSettings().setUserAgentString(defaultUserAgentString);
-                            }
+                    // Set the user agent.
+                    if (userAgentName.equals(getString(R.string.system_default_user_agent))) {  // Use the system default user agent.
+                        // Get the array position of the default user agent name.
+                        int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
+
+                        // Set the user agent according to the system default.
+                        switch (defaultUserAgentArrayPosition) {
+                            case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
+                                // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
+                                mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
+                                break;
+
+                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+                                // Set the user agent to `""`, which uses the default value.
+                                mainWebView.getSettings().setUserAgentString("");
+                                break;
+
+                            case SETTINGS_CUSTOM_USER_AGENT:
+                                // Set the custom user agent.
+                                mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+                                break;
+
+                            default:
+                                // Get the user agent string from the user agent data array
+                                mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
+                        }
+                    } else {  // Set the user agent according to the stored name.
+                        // Get the array position of the user agent name.
+                        int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
+
+                        switch (userAgentArrayPosition) {
+                            case UNRECOGNIZED_USER_AGENT:  // The user agent name contains a custom user agent.
+                                mainWebView.getSettings().setUserAgentString(userAgentName);
+                                break;
+
+                            case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
+                                // Set the user agent to `""`, which uses the default value.
+                                mainWebView.getSettings().setUserAgentString("");
+                                break;
+
+                            default:
+                                // Get the user agent string from the user agent data array.
+                                mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
+                        }
+                    }
+
+                    // Set swipe to refresh.
+                    switch (swipeToRefreshInt) {
+                        case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT:
+                            // Set swipe to refresh according to the default.
+                            swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
                             break;
 
-                        case "WebView default user agent":
-                            // Set the user agent to `""`, which uses the default value.
-                            mainWebView.getSettings().setUserAgentString("");
+                        case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
+                            // Enable swipe to refresh.
+                            swipeRefreshLayout.setEnabled(true);
                             break;
 
-                        default:
-                            // Use the selected user agent.
-                            mainWebView.getSettings().setUserAgentString(userAgentString);
+                        case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
+                            // Disable swipe to refresh.
+                            swipeRefreshLayout.setEnabled(false);
                     }
 
-                    // Store the applied user agent string.
+                    // Store the applied user agent string, which is used in the View Source activity.
                     appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
                 }
 
@@ -3395,6 +3537,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
                 domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
                 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
+                easyListEnabled = sharedPreferences.getBoolean("easylist", true);
+                easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
+                fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboy_annoyance_list", true);
+                fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboy_social_blocking_list", true);
 
                 // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
                 if (nightMode) {
@@ -3407,6 +3553,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
                 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
+                swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
 
                 // Reset the pinned SSL certificate information.
                 domainSettingsDatabaseId = -1;
@@ -3428,23 +3575,32 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
                 // <https://redmine.stoutner.com/issues/160>
                 if (!urlIsLoading) {
-                    switch (defaultUserAgentString) {
-                        case "WebView default user agent":
+                    // Get the array position of the user agent name.
+                    int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
+
+                    // Set the user agent.
+                    switch (userAgentArrayPosition) {
+                        case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
+                            // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
+                            mainWebView.getSettings().setUserAgentString(defaultUserAgentName);
+                            break;
+
+                        case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
                             // Set the user agent to `""`, which uses the default value.
                             mainWebView.getSettings().setUserAgentString("");
                             break;
 
-                        case "Custom user agent":
+                        case SETTINGS_CUSTOM_USER_AGENT:
                             // Set the custom user agent.
                             mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
                             break;
 
                         default:
-                            // Use the selected user agent.
-                            mainWebView.getSettings().setUserAgentString(defaultUserAgentString);
+                            // Get the user agent string from the user agent data array
+                            mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
                     }
 
-                    // Store the applied user agent string.
+                    // Store the applied user agent string, which is used in the View Source activity.
                     appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
                 }
 
@@ -3463,6 +3619,11 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
             if (mainMenu != null) {
                 updatePrivacyIcons(true);
             }
+
+            // Reload the website if returning from the Domains activity.
+            if (reloadWebsite) {
+                mainWebView.reload();
+            }
         }
     }
 
@@ -3568,7 +3729,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
     }
 
     private void loadBookmarksFolder() {
-        // Update `bookmarksCursor` with the contents of the bookmarks database for the current folder.
+        // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
         bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
 
         // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
@@ -3585,7 +3746,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD
                 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
                 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
 
-                // Get the favorite icon byte array from the `Cursor`.
+                // Get the favorite icon byte array from the cursor.
                 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
 
                 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.