X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=ff82699621388e50c22ab34879fef9c3c673369e;hp=8cffc2a56ee8e583978d83f619214486c5a87b4b;hb=2bb01fc27d480292f2e618f0ee5ca1a6277a66e2;hpb=6fdb3adb172e7221723097b656a1f6bb59a1d4dd 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 8cffc2a5..ff826996 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -21,6 +21,7 @@ package com.stoutner.privacybrowser.activities; +import android.Manifest; import android.annotation.SuppressLint; import android.app.DialogFragment; import android.app.DownloadManager; @@ -31,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; @@ -43,6 +45,7 @@ import android.net.http.SslCertificate; import android.net.http.SslError; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; import android.preference.PreferenceManager; import android.print.PrintDocumentAdapter; @@ -52,6 +55,7 @@ import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.NavigationView; import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26. import android.support.v4.content.pm.ShortcutInfoCompat; @@ -109,6 +113,7 @@ import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; import com.stoutner.privacybrowser.dialogs.DownloadImageDialog; +import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; @@ -139,12 +144,12 @@ import java.util.List; import java.util.Map; import java.util.Set; -// We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21. +// AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21. public class MainWebViewActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener, CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, DownloadFileDialog.DownloadFileListener, - DownloadImageDialog.DownloadImageListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, - NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, - UrlHistoryDialog.UrlHistoryListener { + DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, + EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, + PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener { // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, // `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, @@ -301,6 +306,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, . private boolean reapplyDomainSettingsOnRestart; + // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`. + private boolean reapplyAppSettingsOnRestart; + // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`. private String currentDomainName; @@ -387,9 +395,22 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`. private String oldFolderNameString; + // The download strings are used in `onCreate()` and `onRequestPermissionResult()`. + private String downloadUrl; + private String downloadContentDisposition; + private long downloadContentLength; + + // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`. + private String downloadImageUrl; + + // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`. + private final int DOWNLOAD_FILE_REQUEST_CODE = 1; + private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2; + @Override // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers. - @SuppressLint({"SetJavaScriptEnabled"}) + // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`. + @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"}) // Remove Android Studio's warning about deprecations. We have to use the deprecated `getColor()` until API >= 23. @SuppressWarnings("deprecation") protected void onCreate(Bundle savedInstanceState) { @@ -807,6 +828,239 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // drawerToggle creates the hamburger icon at the start of the AppBar. drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer); + // Get a handle for the progress bar. + final ProgressBar progressBar = findViewById(R.id.progress_bar); + + mainWebView.setWebChromeClient(new WebChromeClient() { + // Update the progress bar when a page is loading. + @Override + public void onProgressChanged(WebView view, int progress) { + // Inject the night mode CSS if night mode is enabled. + if (nightMode) { + // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links + // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color. + // `border: none` removes all borders, which can also be used to underline text. + // `a {color: #1565C0}` sets links to be a dark blue. `!important` takes precedent over any existing sub-settings. + mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " + + "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" + + "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> { + // Initialize a `Handler` to display `mainWebView`. + Handler displayWebViewHandler = new Handler(); + + // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied. + Runnable displayWebViewRunnable = () -> { + // Only display `mainWebView` if the progress bar is one. This prevents the display of the `WebView` while it is still loading. + if (progressBar.getVisibility() == View.GONE) { + mainWebView.setVisibility(View.VISIBLE); + } + }; + + // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds. + displayWebViewHandler.postDelayed(displayWebViewRunnable, 500); + }); + } + + // Update the progress bar. + progressBar.setProgress(progress); + + // Set the visibility of the progress bar. + if (progress < 100) { + // Show the progress bar. + progressBar.setVisibility(View.VISIBLE); + } else { + // Hide the progress bar. + progressBar.setVisibility(View.GONE); + + // Display `mainWebView` if night mode is disabled. + // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not + // currently enabled. + if (!nightMode) { + mainWebView.setVisibility(View.VISIBLE); + } + + //Stop the `SwipeToRefresh` indicator if it is running + swipeRefreshLayout.setRefreshing(false); + } + } + + // Set the favorite icon when it changes. + @Override + public void onReceivedIcon(WebView view, Bitmap icon) { + // Only update the favorite icon if the website has finished loading. + if (progressBar.getVisibility() == View.GONE) { + // Save a copy of the favorite icon. + favoriteIconBitmap = icon; + + // Place the favorite icon in the appBar. + favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true)); + } + } + + // Save a copy of the title when it changes. + @Override + public void onReceivedTitle(WebView view, String title) { + // Save a copy of the title. + webViewTitle = title; + } + + // Enter full screen video + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + // Pause the ad if this is the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + BannerAd.pauseAd(adView); + } + + // Remove the translucent overlays. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command. + drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + + /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. + * 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. + */ + rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + // Set `rootCoordinatorLayout` to fill the entire screen. + rootCoordinatorLayout.setFitsSystemWindows(false); + + // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen. + fullScreenVideoFrameLayout.addView(view); + fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); + } + + // Exit full screen video + public void onHideCustomView() { + // Hide `fullScreenVideoFrameLayout`. + fullScreenVideoFrameLayout.removeAllViews(); + fullScreenVideoFrameLayout.setVisibility(View.GONE); + + // Add the translucent status flag. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Set `rootCoordinatorLayout` to fit inside the status and navigation bars. This also clears the `SYSTEM_UI` flags. + rootCoordinatorLayout.setFitsSystemWindows(true); + + // Show the ad if this is the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`. + BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); + + // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. + adView = findViewById(R.id.adview); + } + } + }); + + // Register `mainWebView` for a context menu. This is used to see link targets and download images. + registerForContextMenu(mainWebView); + + // Allow the downloading of files. + mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> { + // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { + // The WRITE_EXTERNAL_STORAGE permission needs to be requested. + + // Store the variables for future use by `onRequestPermissionsResult()`. + downloadUrl = url; + downloadContentDisposition = contentDisposition; + downloadContentLength = contentLength; + + // Show a dialog if the user has previously denied the permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_FILE. + DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE); + + // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed. + downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); + } else { // Show the permission request directly. + // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); + } + } else { // The WRITE_EXTERNAL_STORAGE permission has already been granted. + // Get a handle for the download file alert dialog. + AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength); + + // Show the download file alert dialog. + downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } + }); + + // Allow pinch to zoom. + mainWebView.getSettings().setBuiltInZoomControls(true); + + // Hide zoom controls. + mainWebView.getSettings().setDisplayZoomControls(false); + + // Set `mainWebView` to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen. + mainWebView.getSettings().setUseWideViewPort(true); + + // Set `mainWebView` to load in overview mode (zoomed out to the maximum width). + mainWebView.getSettings().setLoadWithOverviewMode(true); + + // Explicitly disable geolocation. + mainWebView.getSettings().setGeolocationEnabled(false); + + // Initialize cookieManager. + cookieManager = CookieManager.getInstance(); + + // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard). + customHeaders.put("X-Requested-With", ""); + + // Initialize the default preference values the first time the program is run. `this` is the context. `false` keeps this command from resetting any current preferences back to default. + PreferenceManager.setDefaultValues(this, R.xml.preferences, false); + + // Get the intent that started the app. + final Intent launchingIntent = getIntent(); + + // Extract the launching intent data as `launchingIntentUriData`. + final Uri launchingIntentUriData = launchingIntent.getData(); + + // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`. + if (launchingIntentUriData != null) { + formattedUrlString = launchingIntentUriData.toString(); + } + + // Get a handle for the `Runtime`. + privacyBrowserRuntime = Runtime.getRuntime(); + + // Store the application's private data directory. + privateDataDirectoryString = getApplicationInfo().dataDir; + // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`. + + // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode. + inFullScreenBrowsingMode = false; + + // Initialize AdView for the free flavor. + adView = findViewById(R.id.adview); + + // Initialize the privacy settings variables. + javaScriptEnabled = false; + firstPartyCookiesEnabled = false; + thirdPartyCookiesEnabled = false; + domStorageEnabled = false; + saveFormDataEnabled = false; + nightMode = false; + + // Initialize `webViewTitle`. + webViewTitle = getString(R.string.no_title); + + // Initialize `favoriteIconBitmap`. `ContextCompat` must be used until API >= 21. + Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world); + BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable; + assert favoriteIconBitmapDrawable != null; + favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap(); + + // If the favorite icon is null, load the default. + if (favoriteIconBitmap == null) { + favoriteIconBitmap = favoriteIconDefaultBitmap; + } + + // Apply the app settings from the shared preferences. + applyAppSettings(); + // Instantiate the block list helper. BlockListHelper blockListHelper = new BlockListHelper(); @@ -860,7 +1114,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD return true; } else { // Load the URL in Privacy Browser. // Apply the domain settings for the new URL. - applyDomainSettings(url); + applyDomainSettings(url, true); // Returning `false` causes the current `WebView` to handle the URL and prevents it from adding redirects to the history list. return false; @@ -920,8 +1174,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Update the URL in urlTextBox when the page starts to load. @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied. + public void onPageStarted(WebView view, String url, Bitmap favicon) {// If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied. if (nightMode) { mainWebView.setVisibility(View.INVISIBLE); } @@ -942,7 +1195,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Apply any custom domain settings if the URL was loaded by navigating history. if (navigatingHistory) { - applyDomainSettings(url); + applyDomainSettings(url, true); } // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page. @@ -953,6 +1206,11 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load. @Override public void onPageFinished(WebView view, String url) { + // Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions. + if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) { + cookieManager.flush(); + } + // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes. urlIsLoading = false; @@ -992,7 +1250,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD inputMethodManager.showSoftInput(urlTextBox, 0); // Apply the domain settings. This clears any settings from the previous domain. - applyDomainSettings(formattedUrlString); + applyDomainSettings(formattedUrlString, true); } else { // `WebView` has loaded a webpage. // Set `formattedUrlString`. formattedUrlString = url; @@ -1063,7 +1321,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) || !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) || !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) { - // The pinned SSL certificate doesn't match the current domain certificate. + // The pinned SSL certificate doesn't match the current domain certificate. //Display the pinned SSL certificate mismatch `AlertDialog`. AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog(); pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch)); @@ -1094,7 +1352,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) && currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) && currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) { - // An SSL certificate is pinned and matches the current domain certificate. + // An SSL certificate is pinned and matches the current domain certificate. // Proceed to the website without displaying an error. handler.proceed(); } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate. @@ -1108,216 +1366,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD } }); - // Get a handle for the progress bar. - final ProgressBar progressBar = findViewById(R.id.progress_bar); - - mainWebView.setWebChromeClient(new WebChromeClient() { - // Update the progress bar when a page is loading. - @Override - public void onProgressChanged(WebView view, int progress) { - // Inject the night mode CSS if night mode is enabled. - if (nightMode) { - // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links - // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color. - // `border: none` removes all borders, which can also be used to underline text. - // `a {color: #1565C0}` sets links to be a dark blue. `!important` takes precedent over any existing sub-settings. - mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " + - "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" + - "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> { - // Initialize a `Handler` to display `mainWebView`. - Handler displayWebViewHandler = new Handler(); - - // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied. - Runnable displayWebViewRunnable = () -> { - // Only display `mainWebView` if the progress bar is one. This prevents the display of the `WebView` while it is still loading. - if (progressBar.getVisibility() == View.GONE) { - mainWebView.setVisibility(View.VISIBLE); - } - }; - - // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds. - displayWebViewHandler.postDelayed(displayWebViewRunnable, 500); - }); - } - - // Update the progress bar. - progressBar.setProgress(progress); - - // Set the visibility of the progress bar. - if (progress < 100) { - // Show the progress bar. - progressBar.setVisibility(View.VISIBLE); - } else { - // Hide the progress bar. - progressBar.setVisibility(View.GONE); - - // Display `mainWebView` if night mode is disabled. - // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not - // currently enabled. - if (!nightMode) { - mainWebView.setVisibility(View.VISIBLE); - } - - //Stop the `SwipeToRefresh` indicator if it is running - swipeRefreshLayout.setRefreshing(false); - } - } - - // Set the favorite icon when it changes. - @Override - public void onReceivedIcon(WebView view, Bitmap icon) { - // Only update the favorite icon if the website has finished loading. - if (progressBar.getVisibility() == View.GONE) { - // Save a copy of the favorite icon. - favoriteIconBitmap = icon; - - // Place the favorite icon in the appBar. - favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true)); - } - } - - // Save a copy of the title when it changes. - @Override - public void onReceivedTitle(WebView view, String title) { - // Save a copy of the title. - webViewTitle = title; - } - - // Enter full screen video - @Override - public void onShowCustomView(View view, CustomViewCallback callback) { - // Pause the ad if this is the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.pauseAd(adView); - } - - // Remove the translucent overlays. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command. - drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - - /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. - * 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. - */ - rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - - // Set `rootCoordinatorLayout` to fill the entire screen. - rootCoordinatorLayout.setFitsSystemWindows(false); - - // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen. - fullScreenVideoFrameLayout.addView(view); - fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); - } - - // Exit full screen video - public void onHideCustomView() { - // Hide `fullScreenVideoFrameLayout`. - fullScreenVideoFrameLayout.removeAllViews(); - fullScreenVideoFrameLayout.setVisibility(View.GONE); - - // Add the translucent status flag. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`. - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - // Set `rootCoordinatorLayout` to fit inside the status and navigation bars. This also clears the `SYSTEM_UI` flags. - rootCoordinatorLayout.setFitsSystemWindows(true); - - // Show the ad if this is the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`. - BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); - - // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. - adView = findViewById(R.id.adview); - } - } - }); - - // Register `mainWebView` for a context menu. This is used to see link targets and download images. - registerForContextMenu(mainWebView); - - // Allow the downloading of files. - mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> { - // Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`. - AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength); - downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); - }); - - // Allow pinch to zoom. - mainWebView.getSettings().setBuiltInZoomControls(true); - - // Hide zoom controls. - mainWebView.getSettings().setDisplayZoomControls(false); - - // Set `mainWebView` to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen. - mainWebView.getSettings().setUseWideViewPort(true); - - // Set `mainWebView` to load in overview mode (zoomed out to the maximum width). - mainWebView.getSettings().setLoadWithOverviewMode(true); - - // Explicitly disable geolocation. - mainWebView.getSettings().setGeolocationEnabled(false); - - // Initialize cookieManager. - cookieManager = CookieManager.getInstance(); - - // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard). - customHeaders.put("X-Requested-With", ""); - - // Initialize the default preference values the first time the program is run. `this` is the context. `false` keeps this command from resetting any current preferences back to default. - PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - - // Get the intent that started the app. - final Intent launchingIntent = getIntent(); - - // Extract the launching intent data as `launchingIntentUriData`. - final Uri launchingIntentUriData = launchingIntent.getData(); - - // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`. - if (launchingIntentUriData != null) { - formattedUrlString = launchingIntentUriData.toString(); - } - - // Get a handle for the `Runtime`. - privacyBrowserRuntime = Runtime.getRuntime(); - - // Store the application's private data directory. - privateDataDirectoryString = getApplicationInfo().dataDir; - // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`. - - // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode. - inFullScreenBrowsingMode = false; - - // Initialize AdView for the free flavor. - adView = findViewById(R.id.adview); - - // Initialize the privacy settings variables. - javaScriptEnabled = false; - firstPartyCookiesEnabled = false; - thirdPartyCookiesEnabled = false; - domStorageEnabled = false; - saveFormDataEnabled = false; - nightMode = false; - - // Initialize `webViewTitle`. - webViewTitle = getString(R.string.no_title); - - // Initialize `favoriteIconBitmap`. `ContextCompat` must be used until API >= 21. - Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world); - BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable; - assert favoriteIconBitmapDrawable != null; - favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap(); - - // If the favorite icon is null, load the default. - if (favoriteIconBitmap == null) { - favoriteIconBitmap = favoriteIconDefaultBitmap; - } - - // Apply the app settings from the shared preferences. - applyAppSettings(); - - // Load `formattedUrlString` if we are not waiting for Orbot to connect. + // Load the website if not waiting for Orbot to connect. if (!waitingForOrbot) { loadUrl(formattedUrlString); } @@ -1328,22 +1377,28 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity. setIntent(intent); + // Check to see if the intent contains a new URL. if (intent.getData() != null) { // Get the intent data and convert it to a string. final Uri intentUriData = intent.getData(); formattedUrlString = intentUriData.toString(); - } - // Close the navigation drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { - drawerLayout.closeDrawer(GravityCompat.START); - } + // Load the website. + loadUrl(formattedUrlString); - // Load the website. - loadUrl(formattedUrlString); + // Close the navigation drawer if it is open. + if (drawerLayout.isDrawerVisible(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START); + } + + // Close the bookmarks drawer if it is open. + if (drawerLayout.isDrawerVisible(GravityCompat.END)) { + drawerLayout.closeDrawer(GravityCompat.END); + } - // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it. - mainWebView.requestFocus(); + // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it. + mainWebView.requestFocus(); + } } @Override @@ -1351,31 +1406,31 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Run the default commands. super.onRestart(); - // Apply the app settings, which may have been changed in `SettingsActivity`. - applyAppSettings(); + // Apply the app settings if returning from the Settings activity.. + if (reapplyAppSettingsOnRestart) { + // Apply the app settings. + applyAppSettings(); - // Apply the domain settings if returning from the Domains Activity. - if (reapplyDomainSettingsOnRestart) { - // Reset `reapplyDomainSettingsOnRestart`. - reapplyDomainSettingsOnRestart = false; - - // Reapply the domain settings. - applyDomainSettings(formattedUrlString); - } + // Reload the webpage if displaying of images has been disabled in the Settings activity. + if (reloadOnRestart) { + // Reload `mainWebView`. + mainWebView.reload(); - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); + // Reset `reloadOnRestartBoolean`. + reloadOnRestart = false; + } - // Set the display webpage images mode. - setDisplayWebpageImages(); + // Reset the return from settings flag. + reapplyAppSettingsOnRestart = false; + } - // Reload the webpage if displaying of images has been disabled in `SettingsFragment`. - if (reloadOnRestart) { - // Reload `mainWebView`. - mainWebView.reload(); + // Apply the domain settings if returning from the Domains activity. + if (reapplyDomainSettingsOnRestart) { + // Reapply the domain settings. + applyDomainSettings(formattedUrlString, false); - // Reset `reloadOnRestartBoolean`. - reloadOnRestart = false; + // Reset `reapplyDomainSettingsOnRestart`. + reapplyDomainSettingsOnRestart = false; } // Load the URL on restart to apply changes to night mode. @@ -1387,7 +1442,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD loadUrlOnRestart = false; } - // + // Update the bookmarks drawer if returning from the Bookmarks activity. if (restartFromBookmarksActivity) { // Close the bookmarks drawer. drawerLayout.closeDrawer(GravityCompat.END); @@ -1398,6 +1453,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Reset `restartFromBookmarksActivity`. restartFromBookmarksActivity = false; } + + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated. + updatePrivacyIcons(true); } // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`. @@ -2011,7 +2069,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD break; case R.id.domains: - // Reapply the domain settings on returning to `MainWebViewActivity`. + // Set the flag to reapply the domain settings on restart when returning from Domain Settings. reapplyDomainSettingsOnRestart = true; currentDomainName = ""; @@ -2021,7 +2079,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD break; case R.id.settings: - // Reapply the domain settings on returning to `MainWebViewActivity`. + // Set the flag to reapply app settings on restart when returning from Settings. + reapplyAppSettingsOnRestart = true; + + // Set the flag to reapply the domain settings on restart when returning from Settings. reapplyDomainSettingsOnRestart = true; currentDomainName = ""; @@ -2288,9 +2349,31 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Add a `Download Image` entry. menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { - // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`. - AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); - downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { + // The WRITE_EXTERNAL_STORAGE permission needs to be requested. + + // Store the image URL for use by `onRequestPermissionResult()`. + downloadImageUrl = imageUrl; + + // Show a dialog if the user has previously denied the permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. + DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); + + // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. + downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); + } else { // Show the permission request directly. + // Request the permission. The download dialog will be launched by `onRequestPermissionResult(). + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); + } + } else { // The WRITE_EXTERNAL_STORAGE permission has already been granted. + // Get a handle for the download image alert dialog. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); + + // Show the download image alert dialog. + downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } return false; }); @@ -2325,9 +2408,31 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Add a `Download Image` entry. menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { - // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`. - AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); - downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { + // The WRITE_EXTERNAL_STORAGE permission needs to be requested. + + // Store the image URL for use by `onRequestPermissionResult()`. + downloadImageUrl = imageUrl; + + // Show a dialog if the user has previously denied the permission. + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. + // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. + DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE); + + // Show the download location permission alert dialog. The permission will be requested when the dialog is closed. + downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location)); + } else { // Show the permission request directly. + // Request the permission. The download dialog will be launched by `onRequestPermissionResult(). + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); + } + } else { // The WRITE_EXTERNAL_STORAGE permission has already been granted. + // Get a handle for the download image alert dialog. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); + + // Show the download image alert dialog. + downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } return false; }); @@ -2476,6 +2581,58 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null); } + @Override + public void onCloseDownloadLocationPermissionDialog(int downloadType) { + switch (downloadType) { + case DownloadLocationPermissionDialog.DOWNLOAD_FILE: + // Request the WRITE_EXTERNAL_STORAGE permission with a file request code. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE); + break; + + case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE: + // Request the WRITE_EXTERNAL_STORAGE permission with an image request code. + ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE); + break; + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + switch (requestCode) { + case DOWNLOAD_FILE_REQUEST_CODE: + // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status. + AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength); + + // On API 23, displaying the fragment must be delayed or the app will crash. + if (Build.VERSION.SDK_INT == 23) { + new Handler().postDelayed(() -> downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500); + } else { + downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } + + // Reset the download variables. + downloadUrl = ""; + downloadContentDisposition = ""; + downloadContentLength = 0; + break; + + case DOWNLOAD_IMAGE_REQUEST_CODE: + // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl); + + // On API 23, displaying the fragment must be delayed or the app will crash. + if (Build.VERSION.SDK_INT == 23) { + new Handler().postDelayed(() -> downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500); + } else { + downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } + + // Reset the image URL variable. + downloadImageUrl = ""; + break; + } + } + @Override public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) { // Download the image if it has an HTTP or HTTPS URI. @@ -2496,15 +2653,17 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD downloadRequest.addRequestHeader("Cookie", cookies); } - // Get the file name from `dialogFragment`. + // Get the file name from the dialog fragment. EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name); String imageName = downloadImageNameEditText.getText().toString(); - // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`. - if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`. - downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName); - } else { // Only set the title using `imageName`. - downloadRequest.setTitle(imageName); + // Specify the download location. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted. + // Download to the public download directory. + downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName); + } else { // External write permission denied. + // Download to the app's external download directory. + downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName); } // Allow `MediaScanner` to index the download if it is a media file. @@ -2530,7 +2689,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) { // Download the file if it has an HTTP or HTTPS URI. if (downloadUrl.startsWith("http")) { - // Get a handle for the system `DOWNLOAD_SERVICE`. DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); @@ -2547,15 +2705,17 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD downloadRequest.addRequestHeader("Cookie", cookies); } - // Get the file name from `dialogFragment`. + // Get the file name from the dialog fragment. EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name); String fileName = downloadFileNameEditText.getText().toString(); - // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`. - if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download location to `/sdcard/Android/data/com.stoutner.privacybrowser.standard/files` named `fileName`. - downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName); - } else { // Only set the title using `fileName`. - downloadRequest.setTitle(fileName); + // Specify the download location. + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted. + // Download to the public download directory. + downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName); + } else { // External write permission denied. + // Download to the app's external download directory. + downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName); } // Allow `MediaScanner` to index the download if it is a media file. @@ -2818,7 +2978,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD private void loadUrl(String url) { // Apply any custom domain settings. - applyDomainSettings(url); + applyDomainSettings(url, true); // Load the URL. mainWebView.loadUrl(url, customHeaders); @@ -3014,9 +3174,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD } } - // We have to use the deprecated `.getDrawable()` until the minimum API >= 21. + // + // The deprecated `.getDrawable()` must be used until the minimum API >= 21. @SuppressWarnings("deprecation") - private void applyDomainSettings(String url) { + private void applyDomainSettings(String url, boolean resetFavoriteIcon) { // Reset `navigatingHistory`. navigatingHistory = false; @@ -3038,7 +3199,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD loadingNewDomainName = !hostName.equals(currentDomainName); } - // Only apply the domain settings if we are loading a new domain. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc. + // Only apply the domain settings if a new domain is being loaded. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc. if (loadingNewDomainName) { // Set the new `hostname` as the `currentDomainName`. currentDomainName = hostName; @@ -3046,9 +3207,11 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Reset `ignorePinnedSslCertificate`. ignorePinnedSslCertificate = false; - // Reset `favoriteIconBitmap` and display it in the `appbar`. - favoriteIconBitmap = favoriteIconDefaultBitmap; - favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true)); + // Reset the favorite icon if specified. + if (resetFavoriteIcon) { + favoriteIconBitmap = favoriteIconDefaultBitmap; + favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true)); + } // Initialize the database handler. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`. // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. @@ -3454,4 +3617,4 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD bookmarksTitleTextView.setText(currentBookmarksFolder); } } -} +} \ No newline at end of file