]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Add a context menu to delete bookmarks from the database view activity. https:/...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index ec3a24dd19d691accacee5188c3749fcaaba0785..c1463d92f9c845244fb101076ae68fe8390828e0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
@@ -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;
@@ -36,6 +37,7 @@ import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -445,6 +447,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private ForegroundColorSpan initialGrayColorSpan;
     private ForegroundColorSpan finalGrayColorSpan;
 
+    // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
+    private int drawerHeaderPaddingLeftAndRight;
+    private int drawerHeaderPaddingTop;
+    private int drawerHeaderPaddingBottom;
+
     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
     private SslErrorHandler sslErrorHandler;
 
@@ -505,7 +512,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.  The whole premise of Privacy Browser is built around an understanding of these dangers.
     // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
     @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
-    // Remove Android Studio's warning about deprecations.  We have to use the deprecated `getColor()` until API >= 23.
+    // Remove Android Studio's warning about deprecations.  The deprecated `getColor()` must be used until API >= 23.
     @SuppressWarnings("deprecation")
     protected void onCreate(Bundle savedInstanceState) {
         // Get a handle for the shared preferences.
@@ -533,6 +540,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Set the content view.
         setContentView(R.layout.main_drawerlayout);
 
+        // Get a handle for the resources.
+        Resources resources = getResources();
+
         // Get a handle for `inputMethodManager`.
         inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
@@ -549,9 +559,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
         // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
-        redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
-        initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
-        finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+        redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
+        initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
+        finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
 
         // Get a handle for `urlTextBox`.
         urlTextBox = findViewById(R.id.url_edittext);
@@ -577,11 +587,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 {
@@ -637,15 +644,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Set the bookmarks drawer resources according to the theme.  This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
         if (darkTheme) {
-            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
-            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
-            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
-            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
+            launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
+            createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
+            createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
+            bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
         } else {
-            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
-            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
-            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
-            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
+            launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
+            createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
+            createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
+            bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
         }
 
         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
@@ -664,14 +671,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         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));
+            createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder));
         });
 
         // 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();
-            createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
+            createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark));
         });
 
         // Create a double-tap listener to toggle full-screen mode.
@@ -762,7 +769,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Update `findOnPageCountTextView`.
         mainWebView.setFindListener(new WebView.FindListener() {
             // Get a handle for `findOnPageCountTextView`.
-            final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
+            final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
 
             @Override
             public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
@@ -842,8 +849,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
         final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
 
-        // Initialize the bookmarks database helper.  `this` specifies the context.  The two `nulls` do not specify the database name or a `CursorFactory`.
-        // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+        // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
 
         // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
@@ -857,7 +863,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             int databaseID = (int) id;
 
             // Get the bookmark cursor for this ID and move it to the first row.
-            Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
+            Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
             bookmarkCursor.moveToFirst();
 
             // Act upon the bookmark according to the type.
@@ -892,18 +898,30 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
                 AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
-                editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
+                editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder));
             } else {
                 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
                 AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
-                editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
+                editBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_bookmark));
             }
 
             // Consume the event.
             return true;
         });
 
-        // The drawer listener is used to update the navigation menu.
+        // Get the status bar pixel size.
+        int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
+        int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
+
+        // Get the resource density.
+        float screenDensity = resources.getDisplayMetrics().density;
+
+        // Calculate the drawer header padding.  This is used to move the text in the drawer headers below any cutouts.
+        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+
+        // The drawer listener is used to update the navigation menu.`
         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
             @Override
             public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
@@ -920,6 +938,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             @Override
             public void onDrawerStateChanged(int newState) {
                 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) {  // A drawer is opening or closing.
+                    // Get handles for the drawer headers.
+                    TextView navigationHeaderTextView = findViewById(R.id.navigationText);
+                    TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
+
+                    // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
+                    if (navigationHeaderTextView != null) {
+                        navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+                    }
+
+                    // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
+                    if (bookmarksHeaderTextView != null) {
+                        bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
+                    }
+
                     // Update the back, forward, history, and requests menu items.
                     navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
                     navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
@@ -960,7 +992,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                                 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
                                 Runnable displayWebViewRunnable = () -> {
-                                    // Only display `mainWebView` if the progress bar is one.  This prevents the display of the `WebView` while it is still loading.
+                                    // Only display `mainWebView` if the progress bar is gone.  This prevents the display of the `WebView` while it is still loading.
                                     if (progressBar.getVisibility() == View.GONE) {
                                         mainWebView.setVisibility(View.VISIBLE);
                                     }
@@ -1207,17 +1239,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();
 
@@ -1255,7 +1276,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.spinner_item);
-        userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
+        userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
 
         // Apply the app settings from the shared preferences.
         applyAppSettings();
@@ -1590,7 +1611,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Check to see if Privacy Browser is waiting on Orbot.
                 if (!waitingForOrbot) {  // We are not waiting on Orbot, so we need to process the URL.
-                    // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
+                    // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
                     formattedUrlString = url;
 
                     // Display the formatted URL text.
@@ -1638,7 +1659,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             public void onPageFinished(WebView view, String url) {
                 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
                 if (!waitingForOrbot) {
-                    mainWebView.getSettings().setUseWideViewPort(true);
+                    // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
+                    mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
                 }
 
                 // Flush any cookies to persistent storage.  `CookieManager` has become very lazy about flushing cookies in recent versions.
@@ -1703,10 +1725,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Apply the domain settings.  This clears any settings from the previous domain.
                         applyDomainSettings(formattedUrlString, true, false);
                     } else {  // `WebView` has loaded a webpage.
-                        // Set `formattedUrlString`.
-                        formattedUrlString = url;
+                        // Set the formatted URL string.  Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly.
+                        formattedUrlString = mainWebView.getUrl();
 
-                        // Only update `urlTextBox` if the user is not typing in it.
+                        // Only update the URL text box if the user is not typing in it.
                         if (!urlTextBox.hasFocus()) {
                             // Display the formatted URL text.
                             urlTextBox.setText(formattedUrlString);
@@ -1818,6 +1840,32 @@ 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;
+
+            // 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);
@@ -1829,27 +1877,44 @@ 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();
+        // Get the information from the intent.
+        String intentAction = intent.getAction();
+        Uri intentUriData = intent.getData();
 
-            // Load the website.
-            loadUrl(intentUriData.toString());
+        // 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 navigation drawer if it is open.
-            if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
-                drawerLayout.closeDrawer(GravityCompat.START);
+            // Sanitize the search input and convert it to a search.
+            try {
+                encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
+            } catch (UnsupportedEncodingException exception) {
+                encodedUrlString = "";
             }
 
-            // Close the bookmarks drawer if it is open.
-            if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
-                drawerLayout.closeDrawer(GravityCompat.END);
-            }
+            // 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();
+        }
 
-            // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
-            mainWebView.requestFocus();
+        // 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
@@ -2792,21 +2857,39 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
                 return true;
 
+            case R.id.find_on_page:
+                // Hide the URL app bar.
+                supportAppBar.setVisibility(View.GONE);
+
+                // Show the Find on Page `RelativeLayout`.
+                findOnPageLinearLayout.setVisibility(View.VISIBLE);
+
+                // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.
+                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
+                findOnPageEditText.postDelayed(() -> {
+                    // Set the focus on `findOnPageEditText`.
+                    findOnPageEditText.requestFocus();
+
+                    // Display the keyboard.  `0` sets no input flags.
+                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
+                }, 200);
+                return true;
+
+            case R.id.add_to_homescreen:
+                // Show the alert dialog.
+                AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
+                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+
+                //Everything else will be handled by the alert dialog and the associated listener below.
+                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.proxy_through_orbot:
-                // Toggle the proxy through Orbot variable.
-                proxyThroughOrbot = !proxyThroughOrbot;
-
-                // Apply the proxy through Orbot settings.
-                applyProxyThroughOrbot(true);
-                return true;
-
-            case R.id.share:
+            case R.id.share_url:
                 // Setup the share string.
                 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
 
@@ -2816,33 +2899,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 shareIntent.setType("text/plain");
 
                 // Make it so.
-                startActivity(Intent.createChooser(shareIntent, "Share URL"));
+                startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
                 return true;
 
-            case R.id.find_on_page:
-                // Hide the URL app bar.
-                supportAppBar.setVisibility(View.GONE);
+            case R.id.open_with_app:
+                // Create the open with intent with `ACTION_VIEW`.
+                Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
 
-                // Show the Find on Page `RelativeLayout`.
-                findOnPageLinearLayout.setVisibility(View.VISIBLE);
+                // Set the URI but not the MIME type.  This should open all available apps.
+                openWithAppIntent.setData(Uri.parse(formattedUrlString));
 
-                // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.
-                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
-                findOnPageEditText.postDelayed(() -> {
-                    // Set the focus on `findOnPageEditText`.
-                    findOnPageEditText.requestFocus();
+                // Flag the intent to open in a new task.
+                openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                    // Display the keyboard.  `0` sets no input flags.
-                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
-                }, 200);
+                // Show the chooser.
+                startActivity(Intent.createChooser(openWithAppIntent, getString(R.string.open_with)));
                 return true;
 
-            case R.id.add_to_homescreen:
-                // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
-                AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
-                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+            case R.id.open_with_browser:
+                // Create the open with intent with `ACTION_VIEW`.
+                Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
 
-                //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below.
+                // Set the URI and the MIME type.  `"text/html"` should load browser options.
+                openWithBrowserIntent.setDataAndType(Uri.parse(formattedUrlString), "text/html");
+
+                // Flag the intent to open in a new task.
+                openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                // Show the chooser.
+                startActivity(Intent.createChooser(openWithBrowserIntent, getString(R.string.open_with)));
+                return true;
+
+            case R.id.proxy_through_orbot:
+                // Toggle the proxy through Orbot variable.
+                proxyThroughOrbot = !proxyThroughOrbot;
+
+                // Apply the proxy through Orbot settings.
+                applyProxyThroughOrbot(true);
                 return true;
 
             case R.id.refresh:
@@ -3110,7 +3203,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
 
-        // Reload the ad for the free flavor if we not in full screen mode.
+        // Get the status bar pixel size.
+        int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
+
+        // Get the resource density.
+        float screenDensity = getResources().getDisplayMetrics().density;
+
+        // Recalculate the drawer header padding.
+        drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
+        drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
+        drawerHeaderPaddingBottom = (int) (8 * screenDensity);
+
+        // Reload the ad for the free flavor if not in full screen mode.
         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
             // 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_unit_id));
@@ -3383,8 +3488,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Create the bookmark.
         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
 
-        // Update `bookmarksCursor` with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -3428,8 +3533,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Create the folder, which will be placed at the top of the `ListView`.
         bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
 
-        // Update `bookmarksCursor` with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -3643,8 +3748,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
         }
 
-        // Update `bookmarksCursor` with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -3705,8 +3810,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
         }
 
-        // Update `bookmarksCursor` with the current contents of this folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+        // Update the bookmarks cursor with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
         // Update the `ListView`.
         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
@@ -3797,7 +3902,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 drawerLayout.closeDrawer(GravityCompat.END);
             } else {  // A subfolder is displayed.
                 // Place the former parent folder in `currentFolder`.
-                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolder(currentBookmarksFolder);
+                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
 
                 // Load the new folder.
                 loadBookmarksFolder();
@@ -3828,14 +3933,18 @@ 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();
 
         // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
-        if ((Patterns.WEB_URL.matcher(unformattedUrlString).matches()) || (unformattedUrlString.startsWith("http://")) || (unformattedUrlString.startsWith("https://"))) {
-            // Add `https://` at the beginning if it is missing.  Otherwise the app will segfault.
-            if (!unformattedUrlString.startsWith("http")) {
+        if (unformattedUrlString.startsWith("content://")) {
+            // Load the entire content URL.
+            formattedUrlString = unformattedUrlString;
+        } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://")
+                || unformattedUrlString.startsWith("file://")) {
+            // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
+            if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
                 unformattedUrlString = "https://" + unformattedUrlString;
             }
 
@@ -3850,24 +3959,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;
@@ -4554,7 +4675,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // 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.
+        // Set the URI and the MIME type.  Specifying `text/html` displays a good number of options.
         downloadIntent.setDataAndType(Uri.parse(url), "text/html");
 
         // Flag the intent to open in a new task.
@@ -4568,54 +4689,63 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // 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));
+        // Highlight the URL according to the protocol.
+        if (urlString.startsWith("file://")) {  // This is a file URL.
+            // De-emphasize only the protocol.
+            urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        } else if (urlString.startsWith("content://")) {
+            // De-emphasize only the protocol.
+            urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        } else {  // This is a web URL.
+            // Get the index of the `/` immediately after the domain name.
+            int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
 
-        // Create a base URL string.
-        String baseUrl;
+            // 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;
-        }
+            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 last `.` in the domain.
+            int lastDotIndex = baseUrl.lastIndexOf(".");
 
-        // Get the index of the penultimate `.` in the domain.
-        int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
+            // 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);
+            // 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.
-            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);
+                // 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.
+                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);
+                }
             }
-        }
 
-        // De-emphasize the text after the domain name.
-        if (endOfDomainName > 0) {
-            urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            // De-emphasize the text after the domain name.
+            if (endOfDomainName > 0) {
+                urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
         }
     }
 
     private void loadBookmarksFolder() {
         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
-        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+        bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
         // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
         bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {