]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Add Monocles as a search engine. https://redmine.stoutner.com/issues/713
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index d47cb5ae0ffe9cda1ec0048628f913097180b319..52d5f69998a21541177f67a634083bbd7795f0ec 100644 (file)
@@ -158,12 +158,16 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+
 import java.text.NumberFormat;
+
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -247,39 +251,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private ArrayList<List<String[]>> ultraList;
     private ArrayList<List<String[]>> ultraPrivacy;
 
-    // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
+    // Declare the class variables
+    private BroadcastReceiver orbotStatusBroadcastReceiver;
     private String webViewDefaultUserAgent;
-
-    // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
     private boolean incognitoModeEnabled;
-
-    // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
     private boolean fullScreenBrowsingModeEnabled;
-
-    // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
     private boolean inFullScreenBrowsingMode;
-
-    // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`.
+    private boolean downloadWithExternalApp;
     private boolean hideAppBar;
     private boolean scrollAppBar;
-
-    // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
+    private boolean bottomAppBar;
     private boolean loadingNewIntent;
-
-    // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
     private boolean reapplyDomainSettingsOnRestart;
-
-    // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
     private boolean reapplyAppSettingsOnRestart;
-
-    // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
     private boolean displayingFullScreenVideo;
-
-    // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
-    private BroadcastReceiver orbotStatusBroadcastReceiver;
-
-    // The waiting for proxy boolean is used in `onResume()`, `initializeApp()` and `applyProxy()`.
-    private boolean waitingForProxy = false;
+    private boolean waitingForProxy;
 
     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
     private ActionBarDrawerToggle actionBarDrawerToggle;
@@ -315,6 +301,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private boolean sanitizeFacebookClickIds;
     private boolean sanitizeTwitterAmpRedirects;
 
+    // Define the class variables.
+    private long lastScrollUpdate = 0;
+
     // Declare the class views.
     private FrameLayout rootFrameLayout;
     private DrawerLayout drawerLayout;
@@ -404,9 +393,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Get the screenshot preference.
+        // Get the preferences.
         String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
         boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
+        bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
 
         // Get the theme entry values string array.
         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
@@ -442,7 +432,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         setTheme(R.style.PrivacyBrowser);
 
         // Set the content view.
-        setContentView(R.layout.main_framelayout);
+        if (bottomAppBar) {
+            setContentView(R.layout.main_framelayout_bottom_appbar);
+        } else {
+            setContentView(R.layout.main_framelayout_top_appbar);
+        }
 
         // Get handles for the views.
         rootFrameLayout = findViewById(R.id.root_framelayout);
@@ -1411,7 +1405,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Update the menu checkbox.
             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
-            // Update the staus of Fanboy's Social Blocking List.
+            // Update the status of Fanboy's Social Blocking List.
             optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
 
             // Reload the current WebView.
@@ -1729,9 +1723,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Consume the event.
             return true;
         } else if (menuItemId == R.id.save_url) {  // Save URL.
-            // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-            new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                    currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
+            // Check the download preference.
+            if (downloadWithExternalApp) {  // Download with an external app.
+                downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
+            } else {  // Handle the download inside of Privacy Browser.
+                // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                        currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
+            }
 
             // Consume the event.
             return true;
@@ -2129,7 +2128,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
 
             // Add the blocklist versions to the intent.
-            aboutIntent.putExtra("blocklist_versions", blocklistVersions);
+            aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions);
 
             // Make it so.
             startActivity(aboutIntent);
@@ -2242,9 +2241,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save URL entry.
                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptCookies()).execute(linkUrl);
+                    // Check the download preference.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        downloadUrlWithExternalApp(linkUrl);
+                    } else {  // Handle the download inside of Privacy Browser.
+                        // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                    }
 
                     // Consume the event.
                     return true;
@@ -2309,9 +2313,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save Image entry.
                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
-                   // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptCookies()).execute(imageUrl);
+                    // Check the download preference.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        downloadUrlWithExternalApp(imageUrl);
+                    } else {  // Handle the download inside of Privacy Browser.
+                        // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                    }
 
                     // Consume the event.
                     return true;
@@ -2409,9 +2418,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save Image entry.
                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptCookies()).execute(imageUrl);
+                    // Check the download preference.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        downloadUrlWithExternalApp(imageUrl);
+                    } else {  // Handle the download inside of Privacy Browser.
+                        // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                                currentWebView.getAcceptCookies()).execute(imageUrl);
+                    }
 
                     // Consume the event.
                     return true;
@@ -2431,9 +2445,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Add a Save URL entry.
                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
-                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
-                    new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
-                            currentWebView.getAcceptCookies()).execute(linkUrl);
+                    // Check the download preference.
+                    if (downloadWithExternalApp) {  // Download with an external app.
+                        downloadUrlWithExternalApp(linkUrl);
+                    } else {  // Handle the download inside of Privacy Browser.
+                        // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
+                        new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
+                                currentWebView.getAcceptCookies()).execute(linkUrl);
+                    }
 
                     // Consume the event.
                     return true;
@@ -3017,7 +3036,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
-    @Override
+    private void downloadUrlWithExternalApp(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.
+        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.download_with_external_app)));
+    }
+
     public void onSaveWebpage(int saveType, @NonNull String originalUrlString, DialogFragment dialogFragment) {
         // Get the dialog.
         Dialog dialog = dialogFragment.getDialog();
@@ -3110,7 +3142,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 break;
         }
     }
-
+    
+    // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
+    @SuppressLint("ClickableViewAccessibility")
     private void initializeApp() {
         // Get a handle for the input method.
         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -3220,6 +3254,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
 
         // Get handles for views that need to be modified.
+        LinearLayout bookmarksHeaderLinearLayout = findViewById(R.id.bookmarks_header_linearlayout);
         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
@@ -3286,6 +3321,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             }
         });
 
+        // Set a touch listener on the bookmarks header linear layout so that touches don't pass through to the button underneath.
+        bookmarksHeaderLinearLayout.setOnTouchListener((view, motionEvent) -> {
+            // Consume the touch.
+            return true;
+        });
+
         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
         launchBookmarksActivityFab.setOnClickListener(v -> {
             // Get a copy of the favorite icon bitmap.
@@ -3367,7 +3408,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         });
 
         // Implement swipe to refresh.
-        swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
+        swipeRefreshLayout.setOnRefreshListener(() -> {
+            // Check the visibility of the bottom app bar.  Sometimes it is hidden if the WebView is the same size as the visible screen.
+            if (bottomAppBar && scrollAppBar && (appBarLayout.getVisibility() == View.GONE)) {  // The bottom app bar is currently hidden.
+                // Show the app bar.
+                appBarLayout.setVisibility(View.VISIBLE);
+
+                // Disable the refreshing animation.
+                swipeRefreshLayout.setRefreshing(false);
+            } else {  // A bottom app bar is not currently hidden.
+                // Reload the website.
+                currentWebView.reload();
+            }
+        });
 
         // Store the default progress view offsets for use later in `initializeWebView()`.
         defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
@@ -3441,7 +3494,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Find out if the selected bookmark is a folder.
             boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
 
-            if (isFolder) {
+            // Check to see if the bookmark is a folder.
+            if (isFolder) {  // The bookmark is a folder.
                 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
                 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
 
@@ -3450,7 +3504,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Show the edit folder bookmark dialog.
                 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
-            } else {
+            } else {  // The bookmark is not a folder.
                 // Get the bookmark cursor for this ID.
                 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
 
@@ -3459,6 +3513,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // Load the bookmark in a new tab but do not switch to the tab or close the drawer.
                 addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
+
+                // Display a snackbar.
+                Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
             }
 
             // Consume the event.
@@ -3529,6 +3586,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
         proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
+        downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false);
         hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
         scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
 
@@ -3554,51 +3612,48 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Apply the proxy.
         applyProxy(false);
 
-        // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
-        CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-        AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
-        AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
-        AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
-
-        // Add the scrolling behavior to the layout parameters.
-        if (scrollAppBar) {
-            // Enable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
-            toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-            tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
-        } else {
-            // Disable scrolling of the app bar.
-            swipeRefreshLayoutParams.setBehavior(null);
-            toolbarLayoutParams.setScrollFlags(0);
-            findOnPageLayoutParams.setScrollFlags(0);
-            tabsLayoutParams.setScrollFlags(0);
-
-            // Expand the app bar if it is currently collapsed.
-            appBarLayout.setExpanded(true);
-        }
-
-        // Apply the modified layout parameters.
-        swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
-        toolbar.setLayoutParams(toolbarLayoutParams);
-        findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
-        tabsLinearLayout.setLayoutParams(tabsLayoutParams);
+        // Adjust the layout and scrolling parameters if the app bar is at the top of the screen.
+        if (!bottomAppBar) {
+            // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
+            CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+            AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
+            AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
+            AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
+
+            // Add the scrolling behavior to the layout parameters.
+            if (scrollAppBar) {
+                // Enable scrolling of the app bar.
+                swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+                toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+                findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+                tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
+            } else {
+                // Disable scrolling of the app bar.
+                swipeRefreshLayoutParams.setBehavior(null);
+                toolbarLayoutParams.setScrollFlags(0);
+                findOnPageLayoutParams.setScrollFlags(0);
+                tabsLayoutParams.setScrollFlags(0);
+
+                // Expand the app bar if it is currently collapsed.
+                appBarLayout.setExpanded(true);
+            }
 
-        // Set the app bar scrolling for each WebView.
-        for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
-            // Get the WebView tab fragment.
-            WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+            // Set the app bar scrolling for each WebView.
+            for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+                // Get the WebView tab fragment.
+                WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
 
-            // Get the fragment view.
-            View fragmentView = webViewTabFragment.getView();
+                // Get the fragment view.
+                View fragmentView = webViewTabFragment.getView();
 
-            // Only modify the WebViews if they exist.
-            if (fragmentView != null) {
-                // Get the nested scroll WebView from the tab fragment.
-                NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+                // Only modify the WebViews if they exist.
+                if (fragmentView != null) {
+                    // Get the nested scroll WebView from the tab fragment.
+                    NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
 
-                // Set the app bar scrolling.
-                nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
+                    // Set the app bar scrolling.
+                    nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
+                }
             }
         }
 
@@ -4436,11 +4491,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
 
-        // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
+        // Populate the bookmarks cursor adapter.
         bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
             @Override
             public View newView(Context context, Cursor cursor, ViewGroup parent) {
-                // Inflate the individual item layout.  `false` does not attach it to the root.
+                // Inflate the individual item layout.
                 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
             }
 
@@ -5205,19 +5260,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Hide the action bar.
                             actionBar.hide();
 
-                            // Check to see if the app bar is normally scrolled.
-                            if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
-                                // Get the swipe refresh layout parameters.
-                                CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-
-                                // Remove the off-screen scrolling layout.
-                                swipeRefreshLayoutParams.setBehavior(null);
-                            } else {  // The app bar is not scrolled when it is displayed.
-                                // Remove the padding from the top of the swipe refresh layout.
-                                swipeRefreshLayout.setPadding(0, 0, 0, 0);
-
-                                // The swipe refresh circle must be moved above the now removed status bar location.
-                                swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
+                            // Set layout and scrolling parameters if the app bar is at the top of the screen.
+                            if (!bottomAppBar) {
+                                // Check to see if the app bar is normally scrolled.
+                                if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
+                                    // Get the swipe refresh layout parameters.
+                                    CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+
+                                    // Remove the off-screen scrolling layout.
+                                    swipeRefreshLayoutParams.setBehavior(null);
+                                } else {  // The app bar is not scrolled when it is displayed.
+                                    // Remove the padding from the top of the swipe refresh layout.
+                                    swipeRefreshLayout.setPadding(0, 0, 0, 0);
+
+                                    // The swipe refresh circle must be moved above the now removed status bar location.
+                                    swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
+                                }
                             }
                         }
 
@@ -5247,19 +5305,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Show the action bar.
                             actionBar.show();
 
-                            // Check to see if the app bar is normally scrolled.
-                            if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
-                                // Get the swipe refresh layout parameters.
-                                CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
-
-                                // Add the off-screen scrolling layout.
-                                swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
-                            } else {  // The app bar is not scrolled when it is displayed.
-                                // The swipe refresh layout must be manually moved below the app bar layout.
-                                swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
-
-                                // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                                swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                            // Set layout and scrolling parameters if the app bar is at the top of the screen.
+                            if (!bottomAppBar) {
+                                // Check to see if the app bar is normally scrolled.
+                                if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
+                                    // Get the swipe refresh layout parameters.
+                                    CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
+
+                                    // Add the off-screen scrolling layout.
+                                    swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
+                                } else {  // The app bar is not scrolled when it is displayed.
+                                    // The swipe refresh layout must be manually moved below the app bar layout.
+                                    swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
+
+                                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                                }
                             }
                         }
 
@@ -5298,38 +5359,43 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
         // Allow the downloading of files.
         nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
-            // Define a formatted file size string.
-            String formattedFileSizeString;
-
-            // Process the content length if it contains data.
-            if (contentLength > 0) {  // The content length is greater than 0.
-                // Format the content length as a string.
-                formattedFileSizeString = NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes);
-            } else {  // The content length is not greater than 0.
-                // Set the formatted file size string to be `unknown size`.
-                formattedFileSizeString = getString(R.string.unknown_size);
-            }
+            // Check the download preference.
+            if (downloadWithExternalApp) {  // Download with an external app.
+                downloadUrlWithExternalApp(downloadUrl);
+            } else {  // Handle the download inside of Privacy Browser.
+                // Define a formatted file size string.
+                String formattedFileSizeString;
+
+                // Process the content length if it contains data.
+                if (contentLength > 0) {  // The content length is greater than 0.
+                    // Format the content length as a string.
+                    formattedFileSizeString = NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes);
+                } else {  // The content length is not greater than 0.
+                    // Set the formatted file size string to be `unknown size`.
+                    formattedFileSizeString = getString(R.string.unknown_size);
+                }
 
-            // Get the file name from the content disposition.
-            String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
+                // Get the file name from the content disposition.
+                String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
 
-            // Prevent the dialog from displaying if the app window is not visible.
-            // The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
-            while (!activity.getWindow().isActive()) {
-                try {
-                    // The window is not active.  Wait 1 second.
-                    wait(1000);
-                } catch (InterruptedException e) {
-                    // Do nothing.
+                // Prevent the dialog from displaying if the app window is not visible.
+                // The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
+                while (!activity.getWindow().isActive()) {
+                    try {
+                        // The window is not active.  Wait 1 second.
+                        wait(1000);
+                    } catch (InterruptedException e) {
+                        // Do nothing.
+                    }
                 }
-            }
 
-            // Instantiate the save dialog.
-            DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
-                    nestedScrollWebView.getAcceptCookies());
+                // Instantiate the save dialog.
+                DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
+                        nestedScrollWebView.getAcceptCookies());
 
-            // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
-            saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+                // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
+                saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
+            }
         });
 
         // Update the find on page count.
@@ -5358,7 +5424,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.  Also reinforce full screen browsing mode.
         // On API < 23, `getViewTreeObserver().addOnScrollChangedListener()` must be used, but it is a little bit buggy and appears to get garbage collected from time to time.
         if (Build.VERSION.SDK_INT >= 23) {
-            nestedScrollWebView.setOnScrollChangeListener((view, i, i1, i2, i3) -> {
+            nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+                // Set the swipe to refresh status.
                 if (nestedScrollWebView.getSwipeToRefresh()) {
                     // Only enable swipe to refresh if the WebView is scrolled to the top.
                     swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
@@ -5367,6 +5434,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     swipeRefreshLayout.setEnabled(false);
                 }
 
+                //  Set the visibility of the bottom app bar.
+                if (bottomAppBar && scrollAppBar && (Calendar.getInstance().getTimeInMillis() - lastScrollUpdate > 100)) {
+                    if (scrollY - oldScrollY > 25) {  // The WebView was scrolled down.
+                        appBarLayout.setVisibility(View.GONE);
+                    } else if (scrollY - oldScrollY < -25) {  // The WebView was scrolled up.
+                        appBarLayout.setVisibility(View.VISIBLE);
+                    }
+
+                    // Update the last scroll update variable.  This prevents the app bar from flashing on and off at the bottom of the screen.
+                    lastScrollUpdate = Calendar.getInstance().getTimeInMillis();
+                }
+
                 // Reinforce the system UI visibility flags if in full screen browsing mode.
                 // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
                 if (inFullScreenBrowsingMode) {
@@ -5390,7 +5469,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     swipeRefreshLayout.setEnabled(false);
                 }
 
-
                 // Reinforce the system UI visibility flags if in full screen browsing mode.
                 // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
                 if (inFullScreenBrowsingMode) {
@@ -5768,7 +5846,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
 
                     // Process the UltraList results.
-                    if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraLists's blacklist.
+                    if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraList's blacklist.
                         // Add the result to the resource requests.
                         nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
 
@@ -6022,25 +6100,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             @Override
             public void onPageStarted(WebView view, String url, Bitmap favicon) {
-                // Get the preferences.
-                boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
-
-                // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.  This can't be done in `appAppSettings()` because the app bar is not yet populated there.
-                if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
-                    // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
-                    swipeRefreshLayout.setPadding(0, 0, 0, 0);
-
-                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
-                } else {
-                    // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
-                    appBarHeight = appBarLayout.getHeight();
-
-                    // The swipe refresh layout must be manually moved below the app bar layout.
-                    swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
-
-                    // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
-                    swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                // Set the padding and layout settings if the app bar is at the top of the screen.
+                if (!bottomAppBar) {
+                    // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.  This can't be done in `appAppSettings()` because the app bar is not yet populated there.
+                    if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
+                        // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
+                        swipeRefreshLayout.setPadding(0, 0, 0, 0);
+
+                        // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                        swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
+                    } else {
+                        // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
+                        appBarHeight = appBarLayout.getHeight();
+
+                        // The swipe refresh layout must be manually moved below the app bar layout.
+                        swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
+
+                        // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+                        swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
+                    }
                 }
 
                 // Reset the list of resource requests.
@@ -6057,7 +6135,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Display the formatted URL text.
                     urlEditText.setText(url);
 
-                    // Apply text highlighting to `urlTextBox`.
+                    // Apply text highlighting to the URL text box.
                     highlightUrlText();
 
                     // Hide the keyboard.