]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Handle web search intents. https://redmine.stoutner.com/issues/184
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index 5e4a21d9b6200a6508511889831b93868b9bec1c..c2d65ec51447b4a49224aafaef2d192684e25bcc 100644 (file)
@@ -26,6 +26,7 @@ import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.DialogFragment;
 import android.app.DownloadManager;
+import android.app.SearchManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
@@ -75,6 +76,7 @@ import android.text.Editable;
 import android.text.Spanned;
 import android.text.TextWatcher;
 import android.text.style.ForegroundColorSpan;
+import android.util.Log;
 import android.util.Patterns;
 import android.view.ContextMenu;
 import android.view.GestureDetector;
@@ -292,7 +294,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private CoordinatorLayout rootCoordinatorLayout;
 
     // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
-    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `setDisplayWebpageImages()`, and `applyProxyThroughOrbot()`.
+    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
     private WebView mainWebView;
 
     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
@@ -331,9 +333,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and  `applyDomainSettings()`.
     private boolean nightMode;
 
-    // `displayWebpageImagesBoolean` is used in `applyAppSettings()` and `applyDomainSettings()`.
-    private boolean displayWebpageImagesBoolean;
-
     // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
     private String homepage;
 
@@ -398,6 +397,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
     private boolean displayingFullScreenVideo;
 
+    // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
+    private boolean downloadWithExternalApp;
+
     // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
     private String currentDomainName;
 
@@ -410,18 +412,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
     private boolean waitingForOrbot;
 
-    // `domainSettingsApplied` is used in `prepareOptionsMenu()`, `applyDomainSettings()`, and `setDisplayWebpageImages()`.
+    // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
     private boolean domainSettingsApplied;
 
     // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
     private Boolean domainSettingsJavaScriptEnabled;
 
-    // `displayWebpageImagesInt` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
-    private int displayWebpageImagesInt;
-
-    // `onTheFlyDisplayImagesSet` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
-    private boolean onTheFlyDisplayImagesSet;
-
     // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
     private String waitingForOrbotHtmlString;
 
@@ -583,11 +579,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // If the event is a key-down event on the `enter` button, load the URL.
             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                 // Load the URL into the mainWebView and consume the event.
-                try {
-                    loadUrlFromTextBox();
-                } catch (UnsupportedEncodingException e) {
-                    e.printStackTrace();
-                }
+                loadUrlFromTextBox();
+
                 // If the enter key was pressed, consume the event.
                 return true;
             } else {
@@ -821,11 +814,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
-        // Implement swipe to refresh
-        swipeRefreshLayout = findViewById(R.id.swipe_refreshlayout);
-        swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
+        // Implement swipe to refresh.
+        swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
         swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
 
+        // Set the swipe to refresh color according to the theme.
+        if (darkTheme) {
+            swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
+            swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
+        } else {
+            swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
+        }
+
         // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
@@ -928,8 +928,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Hide the keyboard (if displayed).
                     inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
 
-                    // Clear the focus from from the URL text box.
+                    // Clear the focus from from the URL text box and the WebView.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
                     urlTextBox.clearFocus();
+                    mainWebView.clearFocus();
                 }
             }
         });
@@ -1147,32 +1148,37 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Allow the downloading of files.
         mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
-            // 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 = url;
-                downloadContentDisposition = contentDisposition;
-                downloadContentLength = contentLength;
-
-                // 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.
-                    // Instantiate 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 storage permission has already been granted.
-                // Get a handle for the download file alert dialog.
-                AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+            // Check if the download should be processed by an external app.
+            if (downloadWithExternalApp) {  // Download with an external app.
+                openUrlWithExternalApp(url);
+            } else {  // Download with Android's download manager.
+                // 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 storage permission has not been granted.
+                    // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
+
+                    // Store the variables for future use by `onRequestPermissionsResult()`.
+                    downloadUrl = url;
+                    downloadContentDisposition = contentDisposition;
+                    downloadContentLength = contentLength;
+
+                    // 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.
+                        // Instantiate 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 storage permission has already been granted.
+                    // Get a handle for the download file alert dialog.
+                    AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
 
-                // Show the download file alert dialog.
-                downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                    // Show the download file alert dialog.
+                    downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                }
             }
         });
 
@@ -1200,17 +1206,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
 
-        // Get the intent that started the app.
-        final Intent launchingIntent = getIntent();
-
-        // Extract the launching intent data as `launchingIntentUriData`.
-        final Uri launchingIntentUriData = launchingIntent.getData();
-
-        // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`.
-        if (launchingIntentUriData != null) {
-            formattedUrlString = launchingIntentUriData.toString();
-        }
-
         // Get a handle for the `Runtime`.
         privacyBrowserRuntime = Runtime.getRuntime();
 
@@ -1247,7 +1242,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
 
         // Initialize the user agent array adapter and string array.
-        userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.domain_settings_spinner_item);
+        userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
         userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
 
         // Apply the app settings from the shared preferences.
@@ -1287,10 +1282,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     formattedUrlString = "";
 
                     // Apply the domain settings for the new URL.  `applyDomainSettings` doesn't do anything if the domain has not changed.
-                    applyDomainSettings(url, true, false);
+                    boolean userAgentChanged = 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;
+                    // Check if the user agent has changed.
+                    if (userAgentChanged) {
+                        // Manually load the URL.  The changing of the user agent will cause WebView to reload the previous URL.
+                        mainWebView.loadUrl(url, customHeaders);
+
+                        // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
+                        return true;
+                    } else {
+                        // 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);
@@ -1585,11 +1589,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     // Apply any custom domain settings if the URL was loaded by navigating history.
                     if (navigatingHistory) {
+                        // Apply the domain settings.
+                        boolean userAgentChanged = applyDomainSettings(url, true, false);
+
                         // Reset `navigatingHistory`.
                         navigatingHistory = false;
 
-                        // Apply the domain settings.
-                        applyDomainSettings(url, true, false);
+                        // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
+                        if (userAgentChanged) {
+                            loadUrl(formattedUrlString);
+                        }
                     }
 
                     // 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.
@@ -1699,7 +1708,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     sslCertificate = mainWebView.getCertificate();
 
                     // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session.
-                    if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate) {
+                    // Also ignore if changes in the user agent causes an error while navigating history.
+                    if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) {
                         // Initialize the current SSL certificate variables.
                         String currentWebsiteIssuedToCName = "";
                         String currentWebsiteIssuedToOName = "";
@@ -1796,6 +1806,34 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
+        // Get the intent that started the app.
+        Intent launchingIntent = getIntent();
+
+        // Get the information from the intent.
+        String launchingIntentAction = launchingIntent.getAction();
+        Uri launchingIntentUriData = launchingIntent.getData();
+
+        // If the intent action is a web search, perform the search.
+        if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+            // Create an encoded URL string.
+            String encodedUrlString;
+
+            Log.i("Intent", launchingIntent.getStringExtra(SearchManager.QUERY));
+
+            // Sanitize the search input and convert it to a search.
+            try {
+                encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
+            }
+
+            // Add the base search URL.
+            formattedUrlString = searchURL + encodedUrlString;
+        } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
+            // Set the formatted URL string.
+            formattedUrlString = launchingIntentUriData.toString();
+        }
+
         // Load the website if not waiting for Orbot to connect.
         if (!waitingForOrbot) {
             loadUrl(formattedUrlString);
@@ -1807,27 +1845,46 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
         setIntent(intent);
 
-        // Check to see if the intent contains a new URL.
-        if (intent.getData() != null) {
-            // Get the intent data.
-            final Uri intentUriData = intent.getData();
+        Log.i("Intent", intent.getAction());
 
-            // Load the website.
-            loadUrl(intentUriData.toString());
+        // Get the information from the intent.
+        String intentAction = intent.getAction();
+        Uri intentUriData = intent.getData();
 
-            // Close the navigation drawer if it is open.
-            if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
-                drawerLayout.closeDrawer(GravityCompat.START);
-            }
+        // If the intent action is a web search, perform the search.
+        if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
+            // Create an encoded URL string.
+            String encodedUrlString;
 
-            // Close the bookmarks drawer if it is open.
-            if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
-                drawerLayout.closeDrawer(GravityCompat.END);
+            // Sanitize the search input and convert it to a search.
+            try {
+                encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
             }
 
-            // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
-            mainWebView.requestFocus();
+            // Add the base search URL.
+            formattedUrlString = searchURL + encodedUrlString;
+        } else if (intentUriData != null){  // Check to see if the intent contains a new URL.
+            // Set the formatted URL string.
+            formattedUrlString = intentUriData.toString();
+        }
+
+        // Load the URL.
+        loadUrl(formattedUrlString);
+
+        // Close the navigation drawer if it is open.
+        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
+            drawerLayout.closeDrawer(GravityCompat.START);
         }
+
+        // Close the bookmarks drawer if it is open.
+        if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
+            drawerLayout.closeDrawer(GravityCompat.END);
+        }
+
+        // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
+        mainWebView.requestFocus();
     }
 
     @Override
@@ -2725,9 +2782,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 } else {  // Images are not currently loaded automatically.
                     mainWebView.getSettings().setLoadsImagesAutomatically(true);
                 }
-
-                // Set `onTheFlyDisplayImagesSet`.
-                onTheFlyDisplayImagesSet = true;
                 return true;
 
             case R.id.night_mode:
@@ -2746,7 +2800,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
                     // Get the JavaScript preference.
-                    javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
+                    javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
                 }
 
                 // Apply the JavaScript setting to the WebView.
@@ -2792,8 +2846,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 String shareString = webViewTitle + " â€“ " + urlTextBox.getText().toString();
 
                 // Create the share intent.
-                Intent shareIntent = new Intent();
-                shareIntent.setAction(Intent.ACTION_SEND);
+                Intent shareIntent = new Intent(Intent.ACTION_SEND);
                 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
                 shareIntent.setType("text/plain");
 
@@ -3145,32 +3198,35 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // 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.
-                            // Instantiate 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);
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(linkUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The 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.
+                                // Instantiate 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 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));
+                            // Show the download file alert dialog.
+                            downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                        }
                     }
                     return false;
                 });
@@ -3188,7 +3244,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a `Write Email` entry.
                 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
-                    // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                    // 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`.
@@ -3232,30 +3288,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a `Download Image` entry.
                 menu.add(R.string.download_image).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 image URL for use by `onRequestPermissionResult()`.
-                        downloadImageUrl = imageUrl;
-
-                        // 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.
-                            // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                            DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
-
-                            // Show the download location permission alert dialog.  The permission will be requested when 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_IMAGE_REQUEST_CODE);
-                        }
-                    } else {  // The WRITE_EXTERNAL_STORAGE permission has already been granted.
-                        // Get a handle for the download image alert dialog.
-                        AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
+
+                            // 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.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+
+                                // Show the download location permission alert dialog.  The permission will be requested when 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_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
 
-                        // Show the download image alert dialog.
-                        downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                        }
                     }
                     return false;
                 });
@@ -3291,30 +3350,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a `Download Image` entry.
                 menu.add(R.string.download_image).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 image URL for use by `onRequestPermissionResult()`.
-                        downloadImageUrl = imageUrl;
-
-                        // 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.
-                            // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
-                            DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
-
-                            // Show the download location permission alert dialog.  The permission will be requested when 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_IMAGE_REQUEST_CODE);
-                        }
-                    } else {  // The WRITE_EXTERNAL_STORAGE permission has already been granted.
-                        // Get a handle for the download image alert dialog.
-                        AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+                    // Check if the download should be processed by an external app.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        openUrlWithExternalApp(imageUrl);
+                    } else {  // Download with Android's download manager.
+                        // Check to see if the storage permission has already been granted.
+                        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
+                            // Store the image URL for use by `onRequestPermissionResult()`.
+                            downloadImageUrl = imageUrl;
+
+                            // 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.
+                                // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
+                                DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
+
+                                // Show the download location permission alert dialog.  The permission will be requested when 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_IMAGE_REQUEST_CODE);
+                            }
+                        } else {  // The storage permission has already been granted.
+                            // Get a handle for the download image alert dialog.
+                            AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
 
-                        // Show the download image alert dialog.
-                        downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                            // Show the download image alert dialog.
+                            downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                        }
                     }
                     return false;
                 });
@@ -3421,8 +3483,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
 
         // Setup the shortcut intent.
-        Intent shortcutIntent = new Intent();
-        shortcutIntent.setAction(Intent.ACTION_VIEW);
+        Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
         shortcutIntent.setData(Uri.parse(formattedUrlString));
 
         // Create a shortcut info builder.  The shortcut name becomes the shortcut ID.
@@ -3802,7 +3863,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
-    private void loadUrlFromTextBox() throws UnsupportedEncodingException {
+    private void loadUrlFromTextBox() {
         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
         String unformattedUrlString = urlTextBox.getText().toString().trim();
 
@@ -3824,24 +3885,36 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
 
             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
-            final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
-            final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
-            final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
-            final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
-            final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
+            String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
+            String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
+            String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
+            String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
+            String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
 
             // Build the URI.
             Uri.Builder formattedUri = new Uri.Builder();
             formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
 
             // Decode `formattedUri` as a `String` in `UTF-8`.
-            formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
+            try {
+                formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                // Load a blank string.
+                formattedUrlString = "";
+            }
         } else if (unformattedUrlString.isEmpty()){  // Load a blank web site.
             // Load a blank string.
             formattedUrlString = "";
         } else {  // Search for the contents of the URL box.
-            // Sanitize the search input and convert it to a search.
-            final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
+            // Create an encoded URL String.
+            String encodedUrlString;
+
+            // Sanitize the search input.
+            try {
+                encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
+            }
 
             // Add the base search URL.
             formattedUrlString = searchURL + encodedUrlString;
@@ -3906,7 +3979,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
         hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
         translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
-        displayWebpageImagesBoolean = sharedPreferences.getBoolean("display_webpage_images", true);
+        downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
 
         // Apply the proxy through Orbot settings.
         applyProxyThroughOrbot(false);
@@ -3982,7 +4055,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `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, boolean reloadWebsite) {
+    private boolean applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
+        // Get the current user agent.
+        String initialUserAgent = mainWebView.getSettings().getUserAgentString();
+
+        // Initialize a variable to track if the user agent changes.
+        boolean userAgentChanged = false;
+
         // Parse the URL into a URI.
         Uri uri = Uri.parse(url);
 
@@ -4073,13 +4152,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
             // Store the general preference information.
-            String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
-            String defaultUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser");
-            defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
+            String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
+            String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
+            defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value));
             boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
             nightMode = sharedPreferences.getBoolean("night_mode", false);
+            boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
 
-            if (domainSettingsApplied) {  // The url we are loading has custom domain settings.
+            if (domainSettingsApplied) {  // The url has custom domain settings.
                 // Get a cursor for the current host and move it to the first position.
                 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
                 currentHostDomainSettingsCursor.moveToFirst();
@@ -4102,7 +4182,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
                 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));
+                int 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));
@@ -4218,25 +4298,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         }
                     }
 
-                    // 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;
+                    // Store the applied user agent string, which is used in the View Source activity.
+                    appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
 
-                        case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
-                            // Enable swipe to refresh.
-                            swipeRefreshLayout.setEnabled(true);
-                            break;
+                    // Update the user agent change tracker.
+                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
+                }
 
-                        case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
-                            // Disable swipe to refresh.
-                            swipeRefreshLayout.setEnabled(false);
-                    }
+                // 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;
 
-                    // Store the applied user agent string, which is used in the View Source activity.
-                    appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
+                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED:
+                        // Enable swipe to refresh.
+                        swipeRefreshLayout.setEnabled(true);
+                        break;
+
+                    case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED:
+                        // Disable swipe to refresh.
+                        swipeRefreshLayout.setEnabled(false);
+                }
+
+                // Set the loading of webpage images.
+                switch (displayWebpageImagesInt) {
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
+                        mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
+                        break;
+
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
+                        mainWebView.getSettings().setLoadsImagesAutomatically(true);
+                        break;
+
+                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
+                        mainWebView.getSettings().setLoadsImagesAutomatically(false);
+                        break;
                 }
 
                 // Set a green background on `urlTextBox` to indicate that custom domain settings are being used.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
@@ -4247,15 +4345,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }
             } else {  // The new URL does not have custom domain settings.  Load the defaults.
                 // Store the values from `sharedPreferences` in variables.
-                javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
-                firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
-                thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
-                domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
-                saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);  // Form data can be removed once the minimum API >= 26.
+                javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
+                firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies", false);
+                thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
+                domStorageEnabled = sharedPreferences.getBoolean("dom_storage", false);
+                saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
                 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);
+                fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboys_annoyance_list", true);
+                fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboys_social_blocking_list", true);
                 ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
                 blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
 
@@ -4323,8 +4421,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                     // Store the applied user agent string, which is used in the View Source activity.
                     appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
+
+                    // Update the user agent change tracker.
+                    userAgentChanged = !appliedUserAgentString.equals(initialUserAgent);
                 }
 
+                // Set the loading of webpage images.
+                mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
+
                 // Set a transparent background on `urlTextBox`.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
                 urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
             }
@@ -4332,10 +4436,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Close the domains database helper.
             domainsDatabaseHelper.close();
 
-            // Remove the `onTheFlyDisplayImagesSet` flag and set the display webpage images mode.  `true` indicates that custom domain settings are applied.
-            onTheFlyDisplayImagesSet = false;
-            setDisplayWebpageImages();
-
             // Update the privacy icons, but only if `mainMenu` has already been populated.
             if (mainMenu != null) {
                 updatePrivacyIcons(true);
@@ -4346,6 +4446,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         if (reloadWebsite) {
             mainWebView.reload();
         }
+
+        // Return the user agent changed status.
+        return userAgentChanged;
     }
 
     private void applyProxyThroughOrbot(boolean reloadWebsite) {
@@ -4353,12 +4456,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
         // Get the search preferences.
-        String homepageString = sharedPreferences.getString("homepage", "https://searx.me/");
-        String torHomepageString = sharedPreferences.getString("tor_homepage", "http://ulrn6sryqaifefld.onion/");
-        String torSearchString = sharedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q=");
-        String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", "");
-        String searchString = sharedPreferences.getString("search", "https://searx.me/?q=");
-        String searchCustomUrlString = sharedPreferences.getString("search_custom_url", "");
+        String homepageString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
+        String torHomepageString = sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value));
+        String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
+        String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
+        String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
+        String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
 
         // Set the homepage, search, and proxy options.
         if (proxyThroughOrbot) {  // Set the Tor options.
@@ -4437,28 +4540,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
-    private void setDisplayWebpageImages() {
-        if (!onTheFlyDisplayImagesSet) {
-            if (domainSettingsApplied) {  // Custom domain settings are applied.
-                switch (displayWebpageImagesInt) {
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImagesBoolean);
-                        break;
-
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(true);
-                        break;
-
-                    case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
-                        mainWebView.getSettings().setLoadsImagesAutomatically(false);
-                        break;
-                }
-            } else {  // Default settings are applied.
-                mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImagesBoolean);
-            }
-        }
-    }
-
     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
         // Get handles for the menu items.
         MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
@@ -4516,18 +4597,63 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
+    private void openUrlWithExternalApp(String url) {
+        // Create a download intent.  Not specifying the action type will display the maximum number of options.
+        Intent downloadIntent = new Intent();
+
+        // Set the URI and the mime type.  `"*/*"` will display the maximum number of options.
+        downloadIntent.setDataAndType(Uri.parse(url), "text/html");
+
+        // Flag the intent to open in a new task.
+        downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // Show the chooser.
+        startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
+    }
+
     private void highlightUrlText() {
+        // Get the URL string.
         String urlString = urlTextBox.getText().toString();
 
+        // Get the index of the `/` immediately after the domain name.
+        int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
+
+        // Create a base URL string.
+        String baseUrl;
+
+        // Get the base URL.
+        if (endOfDomainName > 0) {  // There is at least one character after the base URL.
+            // Get the base URL.
+            baseUrl = urlString.substring(0, endOfDomainName);
+        } else {  // There are no characters after the base URL.
+            // Set the base URL to be the entire URL string.
+            baseUrl = urlString;
+        }
+
+        // Get the index of the last `.` in the domain.
+        int lastDotIndex = baseUrl.lastIndexOf(".");
+
+        // Get the index of the penultimate `.` in the domain.
+        int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
+
+        // Markup the beginning of the URL.
         if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
             urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+            // De-emphasize subdomains.
+            if (penultimateDotIndex > 0)  {  // There is more than one subdomain in the domain name.
+                urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
         } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
-            urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
+                // De-emphasize the protocol and the additional subdomains.
+                urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {  // There is only one subdomain in the domain name.
+                // De-emphasize only the protocol.
+                urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
         }
 
-        // Get the index of the `/` immediately after the domain name.
-        int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
-
         // De-emphasize the text after the domain name.
         if (endOfDomainName > 0) {
             urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);