From 2bf00b70e1626bb941ec517fc6749f18ddb255c6 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Tue, 8 Jun 2021 15:58:59 -0700 Subject: [PATCH] Fix crashing when a dialog displays while Privacy Browser is not in the foreground. https://redmine.stoutner.com/issues/730 --- .../activities/MainWebViewActivity.java | 138 +++++++++++------- .../adapters/HistoryArrayAdapter.java | 4 +- .../asynctasks/PrepareSaveDialog.java | 23 ++- .../privacybrowser/definitions/History.java | 18 +-- .../definitions/PendingDialog.java | 35 +++++ .../helpers/CheckPinnedMismatchHelper.java | 24 ++- 6 files changed, 152 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/com/stoutner/privacybrowser/definitions/PendingDialog.java diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 52d5f699..e2fb586a 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -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 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> ultraList; private ArrayList> 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 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))); + } } } }); diff --git a/app/src/main/java/com/stoutner/privacybrowser/adapters/HistoryArrayAdapter.java b/app/src/main/java/com/stoutner/privacybrowser/adapters/HistoryArrayAdapter.java index 5150fb32..1d60ae20 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/adapters/HistoryArrayAdapter.java +++ b/app/src/main/java/com/stoutner/privacybrowser/adapters/HistoryArrayAdapter.java @@ -68,8 +68,8 @@ public class HistoryArrayAdapter extends ArrayAdapter { 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) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java index a7d44954..93bd110e 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/PrepareSaveDialog.java @@ -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 { 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. diff --git a/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java b/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java index f37ee7c7..1b0e4481 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java +++ b/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2017 Soren Stoutner . + * Copyright © 2016-2017,2021 Soren Stoutner . * * This file is part of 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 index 00000000..719e599c --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/definitions/PendingDialog.java @@ -0,0 +1,35 @@ +/* + * Copyright © 2021 Soren Stoutner . + * + * This file is part of 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 . + */ + +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 diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/CheckPinnedMismatchHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/CheckPinnedMismatchHelper.java index 5f1ab22f..9deff4ee 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/CheckPinnedMismatchHelper.java +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/CheckPinnedMismatchHelper.java @@ -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 -- 2.45.2