Fix crashing when a dialog displays while Privacy Browser is not in the foreground...
authorSoren Stoutner <soren@stoutner.com>
Tue, 8 Jun 2021 22:58:59 +0000 (15:58 -0700)
committerSoren Stoutner <soren@stoutner.com>
Tue, 8 Jun 2021 22:58:59 +0000 (15:58 -0700)
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/adapters/HistoryArrayAdapter.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java
app/src/main/java/com/stoutner/privacybrowser/definitions/History.java
app/src/main/java/com/stoutner/privacybrowser/definitions/PendingDialog.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/helpers/CheckPinnedMismatchHelper.java

index 52d5f69998a21541177f67a634083bbd7795f0ec..e2fb586a4503b7c7ee1f8f4e050fd5ae291a0a88 100644 (file)
@@ -107,6 +107,7 @@ import androidx.core.content.res.ResourcesCompat;
 import androidx.core.view.GravityCompat;
 import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import androidx.viewpager.widget.ViewPager;
 import androidx.webkit.WebSettingsCompat;
@@ -126,6 +127,7 @@ import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
+import com.stoutner.privacybrowser.definitions.PendingDialog;
 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
@@ -195,6 +197,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
     public static boolean restartFromBookmarksActivity;
 
+    // Define the public static variables.
+    public static ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
+
     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     public static String currentBookmarksFolder;
@@ -251,22 +256,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private ArrayList<List<String[]>> ultraList;
     private ArrayList<List<String[]>> ultraPrivacy;
 
-    // Declare the class variables
-    private BroadcastReceiver orbotStatusBroadcastReceiver;
-    private String webViewDefaultUserAgent;
-    private boolean incognitoModeEnabled;
-    private boolean fullScreenBrowsingModeEnabled;
-    private boolean inFullScreenBrowsingMode;
-    private boolean downloadWithExternalApp;
-    private boolean hideAppBar;
-    private boolean scrollAppBar;
-    private boolean bottomAppBar;
-    private boolean loadingNewIntent;
-    private boolean reapplyDomainSettingsOnRestart;
-    private boolean reapplyAppSettingsOnRestart;
-    private boolean displayingFullScreenVideo;
-    private boolean waitingForProxy;
-
     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
     private ActionBarDrawerToggle actionBarDrawerToggle;
 
@@ -301,6 +290,22 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     private boolean sanitizeFacebookClickIds;
     private boolean sanitizeTwitterAmpRedirects;
 
+    // Declare the class variables
+    private BroadcastReceiver orbotStatusBroadcastReceiver;
+    private String webViewDefaultUserAgent;
+    private boolean incognitoModeEnabled;
+    private boolean fullScreenBrowsingModeEnabled;
+    private boolean inFullScreenBrowsingMode;
+    private boolean downloadWithExternalApp;
+    private boolean hideAppBar;
+    private boolean scrollAppBar;
+    private boolean bottomAppBar;
+    private boolean loadingNewIntent;
+    private boolean reapplyDomainSettingsOnRestart;
+    private boolean reapplyAppSettingsOnRestart;
+    private boolean displayingFullScreenVideo;
+    private boolean waitingForProxy;
+
     // Define the class variables.
     private long lastScrollUpdate = 0;
 
@@ -703,6 +708,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Resume the ad.
             AdHelper.resumeAd(adView);
         }
+
+        // Show any pending dialogs.
+        for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
+            // Get the pending dialog from the array list.
+            PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
+
+            // Show the pending dialog.
+            pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
+        }
+
+        // Clear the pending dialogs array list.
+        pendingDialogsArrayList.clear();
     }
 
     // `onStop()` runs after `onPause()`.  It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
@@ -3209,12 +3226,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Reset the waiting for proxy status.
                     waitingForProxy = false;
 
-                    // Get a handle for the waiting for proxy dialog.
-                    DialogFragment waitingForProxyDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog));
+                    // Get a list of the current fragments.
+                    List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
+
+                    // Check each fragment to see if it is a waiting for proxy dialog.  Sometimes more than one is displayed.
+                    for (int i = 0; i < fragmentList.size(); i++) {
+                        // Get the fragment tag.
+                        String fragmentTag = fragmentList.get(i).getTag();
 
-                    // Dismiss the waiting for proxy dialog if it is displayed.
-                    if (waitingForProxyDialogFragment != null) {
-                        waitingForProxyDialogFragment.dismiss();
+                        // Check to see if it is the waiting for proxy dialog.
+                        if ((fragmentTag!= null) && fragmentTag.equals(getString(R.string.waiting_for_proxy_dialog))) {
+                            // Dismiss the waiting for proxy dialog.
+                            ((DialogFragment) fragmentList.get(i)).dismiss();
+                        }
                     }
 
                     // Reload existing URLs and load any URLs that are waiting for the proxy.
@@ -4304,8 +4328,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             // Get a handle for the waiting for proxy alert dialog.
                             DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog();
 
-                            // Display the waiting for proxy alert dialog.
-                            waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
+                            // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                            try {
+                                // Show the waiting for proxy alert dialog.
+                                waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
+                            } catch (Exception waitingForTorException) {
+                                // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                                pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
+                            }
                         }
                     }
                 } catch (PackageManager.NameNotFoundException exception) {  // Orbot is not installed.
@@ -4314,8 +4344,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Get a handle for the Orbot not installed alert dialog.
                         DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
 
-                        // Display the Orbot not installed alert dialog.
-                        orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                        try {
+                            // Display the Orbot not installed alert dialog.
+                            orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        } catch (Exception orbotNotInstalledException) {
+                            // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                            pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                        }
                     }
                 }
                 break;
@@ -4341,8 +4377,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         // Get a handle for the waiting for proxy alert dialog.
                         DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
 
-                        // Display the I2P not installed alert dialog.
-                        i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
+                        try {
+                            // Display the I2P not installed alert dialog.
+                            i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
+                        } catch (Exception i2pNotInstalledException) {
+                            // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                            pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
+                        }
                     }
                 }
                 break;
@@ -5378,23 +5420,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 // 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.
-                    }
-                }
-
                 // 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));
+                // Try to show the dialog.  The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
+                try {
+                    // 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));
+                } catch (Exception exception) {  // The dialog could not be shown.
+                    // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                    pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog)));
+                }
             }
         });
 
@@ -6346,22 +6383,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Store the SSL error handler.
                     nestedScrollWebView.setSslErrorHandler(handler);
 
-                    // Prevent the dialog from displaying if the app window is not visible.
-                    // The SSL error handler 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 an SSL certificate error alert dialog.
                     DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
 
-                    // Show the SSL certificate error dialog.
-                    sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+                    // Try to show the dialog.  The SSL error handler continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
+                    try {
+                        // Show the SSL certificate error dialog.
+                        sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+                    } catch (Exception exception) {
+                        // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                        pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
+                    }
                 }
             }
         });
index 5150fb322c72fc5c16fc60cea40a1999b3069dc8..1d60ae208c114f48d345e552bffb5ab54af9b207 100644 (file)
@@ -68,8 +68,8 @@ public class HistoryArrayAdapter extends ArrayAdapter<History> {
         assert history != null;
 
         // Set `favoriteIconImageView` and `urlTextView`.
-        favoriteIconImageView.setImageBitmap(history.entryFavoriteIcon);
-        urlTextView.setText(history.entryUrl);
+        favoriteIconImageView.setImageBitmap(history.favoriteIcon);
+        urlTextView.setText(history.url);
 
         // Set the URL text for `currentPage` to be bold.
         if (position == currentPage) {
index a7d44954a6dcb2539957dc23159f1f835b63d031..93bd110e7d5ad5edb4d8a1a23f9adf9f0b58cef1 100644 (file)
@@ -30,6 +30,8 @@ import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.FragmentManager;
 
 import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.activities.MainWebViewActivity;
+import com.stoutner.privacybrowser.definitions.PendingDialog;
 import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
 import com.stoutner.privacybrowser.helpers.ProxyHelper;
 
@@ -198,22 +200,17 @@ public class PrepareSaveDialog extends AsyncTask<String, Void, String[]> {
             return;
         }
 
-        // Prevent the dialog from displaying if the app window is not visible.
-        // The async task continues to function even when the app 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(saveType, urlString, fileStringArray[0], fileStringArray[1], userAgent, cookiesEnabled);
 
-        // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
-        saveDialogFragment.show(fragmentManager, activity.getString(R.string.save_dialog));
+        // Try to show the dialog.  Sometimes the window is not active.
+        try {
+            // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
+            saveDialogFragment.show(fragmentManager, activity.getString(R.string.save_dialog));
+        } catch (Exception exception) {
+            // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+            MainWebViewActivity.pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, activity.getString(R.string.save_dialog)));
+        }
     }
 
     // Content dispositions can contain other text besides the file name, and they can be in any order.
index f37ee7c797daff4c574c335b4d9107eb2c95261a..1b0e44817e373e287995f8cb961e9f067875717c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2017 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2017,2021 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -21,15 +21,15 @@ package com.stoutner.privacybrowser.definitions;
 
 import android.graphics.Bitmap;
 
-// Create a `History` object.
+// Create a History object.
 public class History {
-    // Create the `History` package-local variables.
-    public final Bitmap entryFavoriteIcon;
-    public final String entryUrl;
+    // Declare the class variables.
+    public final Bitmap favoriteIcon;
+    public final String url;
 
-    public History(Bitmap entryFavoriteIcon, String entryUrl){
-        // Populate the package-local variables.
-        this.entryFavoriteIcon = entryFavoriteIcon;
-        this.entryUrl = entryUrl;
+    public History(Bitmap favoriteIcon, String url){
+        // Populate the class variables.
+        this.favoriteIcon = favoriteIcon;
+        this.url = url;
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/definitions/PendingDialog.java b/app/src/main/java/com/stoutner/privacybrowser/definitions/PendingDialog.java
new file mode 100644 (file)
index 0000000..719e599
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.definitions;
+
+import androidx.fragment.app.DialogFragment;
+
+// Create a PendingDialogs object.
+public class PendingDialog {
+    // Declare the class variables.
+    public final DialogFragment dialogFragment;
+    public final String tag;
+
+    public PendingDialog(DialogFragment dialogFragment, String tag) {
+        // Populate the class variables.
+        this.dialogFragment = dialogFragment;
+        this.tag = tag;
+    }
+}
\ No newline at end of file
index 5f1ab22f65c38a0901cc013a9dead62d34f3d13e..9deff4ee91047f6913a93d92f872f1ccb87d3642 100644 (file)
@@ -25,6 +25,9 @@ import android.net.http.SslCertificate;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.FragmentManager;
 
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.activities.MainWebViewActivity;
+import com.stoutner.privacybrowser.definitions.PendingDialog;
 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
 import com.stoutner.privacybrowser.views.NestedScrollWebView;
 
@@ -121,22 +124,17 @@ public class CheckPinnedMismatchHelper {
                 !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
                 !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
 
-            // Prevent the dialog from displaying if the app window is not visible.
-            // The check pinned mismatch helper continues to function even when the app 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.
-                    activity.wait(1000);
-                } catch (InterruptedException e) {
-                    // Do nothing.
-                }
-            }
-
             // Get a handle for the pinned mismatch alert dialog.
             DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(nestedScrollWebView.getWebViewFragmentId());
 
-            // Show the pinned mismatch alert dialog.
-            pinnedMismatchDialogFragment.show(fragmentManager, "Pinned Mismatch");
+            // Try to show the dialog.  Sometimes the window is not active.
+            try {
+                // Show the pinned mismatch alert dialog.
+                pinnedMismatchDialogFragment.show(fragmentManager, activity.getString(R.string.pinned_mismatch));
+            } catch (Exception exception) {
+                // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
+                MainWebViewActivity.pendingDialogsArrayList.add(new PendingDialog(pinnedMismatchDialogFragment, activity.getString(R.string.pinned_mismatch)));
+            }
         }
     }
 }
\ No newline at end of file