]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Add the option to download with an external app. https://redmine.stoutner.com/issues/698
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index b00e19a7e05c24d5c3e7bf0341c2a2cfdf6d19b7..ed25a37d28b3d7689ec18fa45285ff4ebe85de80 100644 (file)
@@ -247,39 +247,20 @@ 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 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;
@@ -537,6 +518,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
             // Only process the URI if it contains data or it is a web search.  If the user pressed the desktop icon after the app was already running the URI will be null.
             if (intentUriData != null || intentStringExtra != null || isWebSearch) {
+                // Exit the full screen video if it is displayed.
+                if (displayingFullScreenVideo) {
+                    // Exit full screen video mode.
+                    exitFullScreenVideo();
+
+                    // Reload the current WebView.  Otherwise, it can display entirely black.
+                    currentWebView.reload();
+                }
+
                 // Get the shared preferences.
                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
@@ -1720,9 +1710,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;
@@ -2233,9 +2228,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;
@@ -2300,9 +2300,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;
@@ -2400,9 +2405,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;
@@ -2422,9 +2432,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;
@@ -2693,62 +2708,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // close the bookmarks drawer.
             drawerLayout.closeDrawer(GravityCompat.END);
         } else if (displayingFullScreenVideo) {  // A full screen video is shown.
-            // Re-enable the screen timeout.
-            fullScreenVideoFrameLayout.setKeepScreenOn(false);
-
-            // Unset the full screen video flag.
-            displayingFullScreenVideo = false;
-
-            // Remove all the views from the full screen video frame layout.
-            fullScreenVideoFrameLayout.removeAllViews();
-
-            // Hide the full screen video frame layout.
-            fullScreenVideoFrameLayout.setVisibility(View.GONE);
-
-            // Enable the sliding drawers.
-            drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
-            // Show the main content relative layout.
-            mainContentRelativeLayout.setVisibility(View.VISIBLE);
-
-            // Apply the appropriate full screen mode flags.
-            if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-                // Hide the banner ad in the free flavor.
-                if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
-                    View adView = findViewById(R.id.adview);
-
-                    // Hide the banner ad.
-                    AdHelper.hideAd(adView);
-                }
-
-                /* Hide the system bars.
-                 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                 */
-                rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
-                // Reload the website if the app bar is hidden.  Otherwise, there is some bug in Android that causes the WebView to be entirely black.
-                if (hideAppBar) {
-                    // Reload the WebView.
-                    currentWebView.reload();
-                }
-            } else {  // Switch to normal viewing mode.
-                // Remove the `SYSTEM_UI` flags from the root frame layout.
-                rootFrameLayout.setSystemUiVisibility(0);
-            }
-
-            // Reload the ad for the free flavor if not in full screen mode.
-            if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-                // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
-                View adView = findViewById(R.id.adview);
-
-                // Reload the ad.  `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
-                AdHelper.loadAd(adView, getApplicationContext(), this, getString(R.string.ad_unit_id));
-            }
+            // Exit the full screen video.
+            exitFullScreenVideo();
         } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
             // Get the current web back forward list.
             WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
@@ -3062,7 +3023,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();
@@ -3155,7 +3129,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);
@@ -3265,6 +3241,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);
@@ -3331,6 +3308,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.
@@ -3486,7 +3469,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));
 
@@ -3495,7 +3479,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);
 
@@ -3504,6 +3488,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.
@@ -3574,6 +3561,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);
 
@@ -4481,11 +4469,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);
             }
 
@@ -4779,6 +4767,68 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
         }
     }
 
+    private void exitFullScreenVideo() {
+        // Re-enable the screen timeout.
+        fullScreenVideoFrameLayout.setKeepScreenOn(false);
+
+        // Unset the full screen video flag.
+        displayingFullScreenVideo = false;
+
+        // Remove all the views from the full screen video frame layout.
+        fullScreenVideoFrameLayout.removeAllViews();
+
+        // Hide the full screen video frame layout.
+        fullScreenVideoFrameLayout.setVisibility(View.GONE);
+
+        // Enable the sliding drawers.
+        drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
+
+        // Show the main content relative layout.
+        mainContentRelativeLayout.setVisibility(View.VISIBLE);
+
+        // Apply the appropriate full screen mode flags.
+        if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
+            // Hide the app bar if specified.
+            if (hideAppBar) {
+                // Hide the tab linear layout.
+                tabsLinearLayout.setVisibility(View.GONE);
+
+                // Hide the action bar.
+                actionBar.hide();
+            }
+
+            // Hide the banner ad in the free flavor.
+            if (BuildConfig.FLAVOR.contentEquals("free")) {
+                // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+                View adView = findViewById(R.id.adview);
+
+                // Hide the banner ad.
+                AdHelper.hideAd(adView);
+            }
+
+            /* Hide the system bars.
+             * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+             * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+             * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+             */
+            rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                    View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+        } else {  // Switch to normal viewing mode.
+            // Remove the `SYSTEM_UI` flags from the root frame layout.
+            rootFrameLayout.setSystemUiVisibility(0);
+        }
+
+        // Reload the ad for the free flavor if not in full screen mode.
+        if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+            // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
+            View adView = findViewById(R.id.adview);
+
+            // Reload the ad.
+            AdHelper.loadAd(adView, this, this, getString(R.string.ad_unit_id));
+        }
+    }
+
     private void clearAndExit() {
         // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
@@ -5281,38 +5331,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.
@@ -5521,65 +5576,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Exit full screen video.
             @Override
             public void onHideCustomView() {
-                // Re-enable the screen timeout.
-                fullScreenVideoFrameLayout.setKeepScreenOn(false);
-
-                // Unset the full screen video flag.
-                displayingFullScreenVideo = false;
-
-                // Remove all the views from the full screen video frame layout.
-                fullScreenVideoFrameLayout.removeAllViews();
-
-                // Hide the full screen video frame layout.
-                fullScreenVideoFrameLayout.setVisibility(View.GONE);
-
-                // Enable the sliding drawers.
-                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
-
-                // Show the main content relative layout.
-                mainContentRelativeLayout.setVisibility(View.VISIBLE);
-
-                // Apply the appropriate full screen mode flags.
-                if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
-                    // Hide the app bar if specified.
-                    if (hideAppBar) {
-                        // Hide the tab linear layout.
-                        tabsLinearLayout.setVisibility(View.GONE);
-
-                        // Hide the action bar.
-                        actionBar.hide();
-                    }
-
-                    // Hide the banner ad in the free flavor.
-                    if (BuildConfig.FLAVOR.contentEquals("free")) {
-                        // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
-                        View adView = findViewById(R.id.adview);
-
-                        // Hide the banner ad.
-                        AdHelper.hideAd(adView);
-                    }
-
-                    /* Hide the system bars.
-                     * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                     * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
-                     * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                     * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
-                     */
-                    rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-                            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-                } else {  // Switch to normal viewing mode.
-                    // Remove the `SYSTEM_UI` flags from the root frame layout.
-                    rootFrameLayout.setSystemUiVisibility(0);
-                }
-
-                // Reload the ad for the free flavor if not in full screen mode.
-                if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
-                    // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
-                    View adView = findViewById(R.id.adview);
-
-                    // Reload the ad.  `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
-                    AdHelper.loadAd(adView, getApplicationContext(), activity, getString(R.string.ad_unit_id));
-                }
+                // Exit the full screen video.
+                exitFullScreenVideo();
             }
 
             // Upload files.