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=02b7025580a541e409dead26d4e87cf3fc64157e;hp=0bf8d0d6e0929f9cc7efbe4e1fb468b54dba7f96;hb=6ccecb3374c1988aef2650a87dac20923ce3aa2f;hpb=7aacd0e844f74925ddbd278c567d17da79775a67 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 0bf8d0d6..02b70255 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -96,6 +96,7 @@ import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; import android.webkit.WebViewDatabase; +import android.widget.ArrayAdapter; import android.widget.CursorAdapter; import android.widget.EditText; import android.widget.FrameLayout; @@ -107,9 +108,9 @@ import android.widget.RadioButton; import android.widget.RelativeLayout; import android.widget.TextView; -import com.stoutner.privacybrowser.BannerAd; import com.stoutner.privacybrowser.BuildConfig; import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.dialogs.AdConsentDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; @@ -121,6 +122,7 @@ import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; +import com.stoutner.privacybrowser.helpers.AdHelper; import com.stoutner.privacybrowser.helpers.BlockListHelper; import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; @@ -183,7 +185,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()` public static boolean reloadOnRestart; - // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`. + // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`. It is also used in `onRestart()`. public static boolean loadUrlOnRestart; // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`. @@ -212,6 +214,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public static Date pinnedDomainSslStartDate; public static Date pinnedDomainSslEndDate; + // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`. + public final static int UNRECOGNIZED_USER_AGENT = -1; + public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1; + public final static int SETTINGS_CUSTOM_USER_AGENT = 12; + public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0; + public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2; + public final static int DOMAINS_CUSTOM_USER_AGENT = 13; + // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applyAppSettings()`. private ActionBar appBar; @@ -235,7 +245,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`. private FrameLayout fullScreenVideoFrameLayout; - // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`. + // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsMenuSelected()`, and `onRestart()`. private SwipeRefreshLayout swipeRefreshLayout; // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`. @@ -268,9 +278,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `nightMode` is used in `onCreate()` and `applyDomainSettings()`. private boolean nightMode; - // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applyAppSettings()`. - private boolean swipeToRefreshEnabled; - // `displayWebpageImagesBoolean` is used in `applyAppSettings()` and `applyDomainSettings()`. private boolean displayWebpageImagesBoolean; @@ -290,7 +297,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private Runtime privacyBrowserRuntime; // `proxyThroughOrbot` is used in `onRestart()` and `applyAppSettings()`. - boolean proxyThroughOrbot; + private boolean proxyThroughOrbot; // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`. private boolean incognitoModeEnabled; @@ -360,9 +367,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private ForegroundColorSpan initialGrayColorSpan; private ForegroundColorSpan finalGrayColorSpan; - // `adView` is used in `onCreate()` and `onConfigurationChanged()`. - private View adView; - // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`. private SslErrorHandler sslErrorHandler; @@ -410,6 +414,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`. private String downloadImageUrl; + // The user agent variables are used in `onCreate()` and `applyDomainSettings()`. + private ArrayAdapter userAgentNamesArray; + private String[] userAgentDataArray; + // 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; @@ -564,14 +572,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook startActivity(bookmarksIntent); }); - // Set the create new bookmark folder FAB to display the `AlertDialog`. + // Set the create new bookmark folder FAB to display an alert dialog. createBookmarkFolderFab.setOnClickListener(v -> { // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`. AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog(); createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder)); }); - // Set the create new bookmark FAB to display the `AlertDialog`. + // Set the create new bookmark FAB to display an alert dialog. createBookmarkFab.setOnClickListener(view -> { // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`. AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog(); @@ -591,9 +599,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Hide the `appBar`. appBar.hide(); - // Hide the `BannerAd` in the free flavor. + // Hide the banner ad in the free flavor. if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.hideAd(adView); + // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. + AdHelper.hideAd(findViewById(R.id.adview)); } // Modify the system bars. @@ -628,11 +637,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the `BannerAd` in 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); + // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. + AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id)); } // Remove the translucent navigation bar flag if it is set. @@ -754,7 +760,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Convert the id from long to int to match the format of the bookmarks database. int databaseID = (int) id; - // Get the bookmark `Cursor` for this ID and move it to the first row. + // Get the bookmark cursor for this ID and move it to the first row. Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID); bookmarkCursor.moveToFirst(); @@ -885,7 +891,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mainWebView.setVisibility(View.VISIBLE); } - //Stop the `SwipeToRefresh` indicator if it is running + //Stop the swipe to refresh indicator if it is running swipeRefreshLayout.setRefreshing(false); } } @@ -915,7 +921,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public void onShowCustomView(View view, CustomViewCallback callback) { // Pause the ad if this is the free flavor. if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.pauseAd(adView); + // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. + AdHelper.pauseAd(findViewById(R.id.adview)); } // Remove the translucent overlays. @@ -953,11 +960,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); + // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. + AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id)); } } @@ -1058,9 +1062,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -1069,10 +1070,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook saveFormDataEnabled = false; nightMode = false; - // Initialize `webViewTitle`. + // Initialize the WebView title. webViewTitle = getString(R.string.no_title); - // Initialize `favoriteIconBitmap`. `ContextCompat` must be used until API >= 21. + // Initialize the favorite icon bitmap. `ContextCompat` must be used until API >= 21. Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world); BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable; assert favoriteIconBitmapDrawable != null; @@ -1083,6 +1084,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook favoriteIconBitmap = favoriteIconDefaultBitmap; } + // Initialize the user agent array adapter and string array. + userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.domain_settings_spinner_item); + userAgentDataArray = getResources().getStringArray(R.array.user_agent_data); + // Apply the app settings from the shared preferences. applyAppSettings(); @@ -1109,7 +1114,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("http")) { // Load the URL in Privacy Browser. // Apply the domain settings for the new URL. - applyDomainSettings(url, true); + applyDomainSettings(url, true, false); // Returning false causes the current `WebView` to handle the URL and prevents it from adding redirects to the history list. return false; @@ -1242,7 +1247,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Apply any custom domain settings if the URL was loaded by navigating history. if (navigatingHistory) { - applyDomainSettings(url, true); + applyDomainSettings(url, true, false); } // 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. @@ -1298,7 +1303,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook inputMethodManager.showSoftInput(urlTextBox, 0); // Apply the domain settings. This clears any settings from the previous domain. - applyDomainSettings(formattedUrlString, true); + applyDomainSettings(formattedUrlString, true, false); } else { // `WebView` has loaded a webpage. // Set `formattedUrlString`. formattedUrlString = url; @@ -1487,7 +1492,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Apply the domain settings if returning from the Domains activity. if (reapplyDomainSettingsOnRestart) { // Reapply the domain settings. - applyDomainSettings(formattedUrlString, false); + applyDomainSettings(formattedUrlString, false, true); // Reset `reapplyDomainSettingsOnRestart`. reapplyDomainSettingsOnRestart = false; @@ -1532,7 +1537,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Resume the adView for the free flavor. if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.resumeAd(adView); + // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. + AdHelper.resumeAd(findViewById(R.id.adview)); } } @@ -1546,7 +1552,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Pause the adView or it will continue to consume resources in the background on the free flavor. if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.pauseAd(adView); + // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. + AdHelper.pauseAd(findViewById(R.id.adview)); } super.onPause(); @@ -1602,8 +1609,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage); MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size); + MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh); MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images); - MenuItem refreshMenuItem = menu.findItem(R.id.refresh); + MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent); // Set the text for the domain menu item. if (domainSettingsApplied) { @@ -1617,6 +1625,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled); toggleDomStorageMenuItem.setChecked(domStorageEnabled); toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); + swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled()); displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically()); // Enable third-party cookies if first-party cookies are enabled. @@ -1709,8 +1718,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook fontSizeMenuItem.setTitle(fontSizeTitle); selectedFontSizeMenuItem.setChecked(true); - // Only show `Refresh` if `swipeToRefresh` is disabled. - refreshMenuItem.setVisible(!swipeToRefreshEnabled); + // Only show Ad Consent if this is the free flavor. + adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free")); // Run all the other default commands. super.onPrepareOptionsMenu(menu); @@ -1730,6 +1739,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the commands that relate to the menu entries. switch (menuItemId) { + case R.id.toggle_javascript: + // Switch the status of javaScriptEnabled. + javaScriptEnabled = !javaScriptEnabled; + + // Apply the new JavaScript status. + mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); + + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + updatePrivacyIcons(true); + + // Display a `Snackbar`. + if (javaScriptEnabled) { // JavaScrip is enabled. + Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); + } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled. + Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); + } else { // Privacy mode. + Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); + } + + // Reload the WebView. + mainWebView.reload(); + return true; + case R.id.add_or_edit_domain: if (domainSettingsApplied) { // Edit the current domain settings. // Reapply the domain settings on returning to `MainWebViewActivity`. @@ -1739,7 +1771,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Create an intent to launch the domains activity. Intent domainsIntent = new Intent(this, DomainsActivity.class); - // Put extra information instructing the domains activity to directly load the current domain and close on back instread of returning to the domains list. + // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list. domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId); domainsIntent.putExtra("closeOnBack", true); @@ -1772,29 +1804,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } return true; - case R.id.toggle_javascript: - // Switch the status of javaScriptEnabled. - javaScriptEnabled = !javaScriptEnabled; - - // Apply the new JavaScript status. - mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Display a `Snackbar`. - if (javaScriptEnabled) { // JavaScrip is enabled. - Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled. - Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); - } else { // Privacy mode. - Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the WebView. - mainWebView.reload(); - return true; - case R.id.toggle_first_party_cookies: // Switch the status of firstPartyCookiesEnabled. firstPartyCookiesEnabled = !firstPartyCookiesEnabled; @@ -1811,7 +1820,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display a `Snackbar`. if (firstPartyCookiesEnabled) { // First-party cookies are enabled. Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (javaScriptEnabled){ // JavaScript is still enabled. + } else if (javaScriptEnabled) { // JavaScript is still enabled. Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); } else { // Privacy mode. Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); @@ -2017,6 +2026,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mainWebView.getSettings().setTextZoom(200); return true; + case R.id.swipe_to_refresh: + // Toggle swipe to refresh. + swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled()); + return true; + case R.id.display_images: if (mainWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically. mainWebView.getSettings().setLoadsImagesAutomatically(false); @@ -2029,6 +2043,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook onTheFlyDisplayImagesSet = true; return true; + case R.id.view_source: + // Launch the View Source activity. + Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); + startActivity(viewSourceIntent); + return true; + case R.id.share: // Setup the share string. String shareString = webViewTitle + " – " + urlTextBox.getText().toString(); @@ -2075,12 +2095,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); return true; - case R.id.view_source: - // Launch the Vew Source activity. - Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); - startActivity(viewSourceIntent); - return true; - case R.id.add_to_homescreen: // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`. AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog(); @@ -2093,6 +2107,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mainWebView.reload(); return true; + case R.id.ad_consent: + // Display the ad consent dialog. + DialogFragment adConsentDialogFragment = new AdConsentDialog(); + adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent)); + return true; + default: // Don't consume the event. return super.onOptionsItemSelected(menuItem); @@ -2322,11 +2342,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the ad for the free flavor if we are not in full screen mode. if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) { - // Reload the ad. - 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); + // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. + AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id)); } // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug: @@ -2358,14 +2375,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the target URL as the title of the `ContextMenu`. menu.setHeaderTitle(linkUrl); - // Add a `Load URL` entry. - menu.add(R.string.load_url).setOnMenuItemClickListener(item -> { + // Add a Load URL entry. + menu.add(R.string.load_url).setOnMenuItemClickListener((MenuItem item) -> { loadUrl(linkUrl); return false; }); - // Add a `Copy URL` entry. - menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> { + // Add a Copy URL entry. + menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> { // Save the link URL in a `ClipData`. ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl); @@ -2374,6 +2391,38 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return false; }); + // Add a Download URL entry. + menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> { + // 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 = linkUrl; + downloadContentDisposition = "none"; + downloadContentLength = -1; + + // 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(linkUrl, "none", -1); + + // Show the download file alert dialog. + downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + } + return false; + }); + // Add a `Cancel` entry, which by default closes the `ContextMenu`. menu.add(R.string.cancel); break; @@ -3043,7 +3092,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private void loadUrl(String url) { // Apply any custom domain settings. - applyDomainSettings(url, true); + applyDomainSettings(url, true, false); // Load the URL. mainWebView.loadUrl(url, customHeaders); @@ -3096,7 +3145,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false); hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false); translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true); - swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh", false); displayWebpageImagesBoolean = sharedPreferences.getBoolean("display_webpage_images", true); // Set the homepage, search, and proxy options. @@ -3164,9 +3212,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook waitingForOrbot = false; } - // Set swipe to refresh. - swipeRefreshLayout.setEnabled(swipeToRefreshEnabled); - // Set Do Not Track status. if (doNotTrackEnabled) { customHeaders.put("DNT", "1"); @@ -3214,11 +3259,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the `BannerAd` in 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); + // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. + AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.ad_id)); } // Remove the translucent navigation bar flag if it is set. @@ -3235,10 +3277,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // + // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled. // The deprecated `.getDrawable()` must be used until the minimum API >= 21. @SuppressWarnings("deprecation") - private void applyDomainSettings(String url, boolean resetFavoriteIcon) { + private void applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) { // Reset `navigatingHistory`. navigatingHistory = false; @@ -3327,8 +3369,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store the general preference information. String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); - String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); + String defaultUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser"); String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); nightMode = sharedPreferences.getBoolean("night_mode", false); if (domainSettingsApplied) { // The url we are loading has custom domain settings. @@ -3347,10 +3390,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); - String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); + String userAgentName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); - displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); + int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)); + displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); @@ -3413,37 +3457,70 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. // if (!urlIsLoading) { - switch (userAgentString) { - case "System default user agent": - // Set the user agent according to the system default. - switch (defaultUserAgentString) { - case "WebView default user agent": - // Set the user agent to `""`, which uses the default value. - mainWebView.getSettings().setUserAgentString(""); - break; - - case "Custom user agent": - // Set the custom user agent. - mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); - break; - - default: - // Use the selected user agent. - mainWebView.getSettings().setUserAgentString(defaultUserAgentString); - } + // Set the user agent. + if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent. + // Get the array position of the default user agent name. + int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); + + // Set the user agent according to the system default. + switch (defaultUserAgentArrayPosition) { + case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list. + // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. + mainWebView.getSettings().setUserAgentString(defaultUserAgentName); + break; + + case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: + // Set the user agent to `""`, which uses the default value. + mainWebView.getSettings().setUserAgentString(""); + break; + + case SETTINGS_CUSTOM_USER_AGENT: + // Set the custom user agent. + mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); + break; + + default: + // Get the user agent string from the user agent data array + mainWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]); + } + } else { // Set the user agent according to the stored name. + // Get the array position of the user agent name. + int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName); + + switch (userAgentArrayPosition) { + case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent. + mainWebView.getSettings().setUserAgentString(userAgentName); + break; + + case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: + // Set the user agent to `""`, which uses the default value. + mainWebView.getSettings().setUserAgentString(""); + break; + + default: + // Get the user agent string from the user agent data array. + mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); + } + } + + // Set swipe to refresh. + switch (swipeToRefreshInt) { + case DomainsDatabaseHelper.SWIPE_TO_REFRESH_SYSTEM_DEFAULT: + // Set swipe to refresh according to the default. + swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); break; - case "WebView default user agent": - // Set the user agent to `""`, which uses the default value. - mainWebView.getSettings().setUserAgentString(""); + case DomainsDatabaseHelper.SWIPE_TO_REFRESH_ENABLED: + // Enable swipe to refresh. + swipeRefreshLayout.setEnabled(true); break; - default: - // Use the selected user agent. - mainWebView.getSettings().setUserAgentString(userAgentString); + case DomainsDatabaseHelper.SWIPE_TO_REFRESH_DISABLED: + // Disable swipe to refresh. + swipeRefreshLayout.setEnabled(false); } - // Store the applied user agent string. + // Store the applied user agent string, which is used in the View Source activity. appliedUserAgentString = mainWebView.getSettings().getUserAgentString(); } @@ -3476,6 +3553,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + swipeRefreshLayout.setEnabled(defaultSwipeToRefresh); // Reset the pinned SSL certificate information. domainSettingsDatabaseId = -1; @@ -3497,23 +3575,32 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. // if (!urlIsLoading) { - switch (defaultUserAgentString) { - case "WebView default user agent": + // Get the array position of the user agent name. + int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); + + // Set the user agent. + switch (userAgentArrayPosition) { + case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list. + // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names. + mainWebView.getSettings().setUserAgentString(defaultUserAgentName); + break; + + case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT: // Set the user agent to `""`, which uses the default value. mainWebView.getSettings().setUserAgentString(""); break; - case "Custom user agent": + case SETTINGS_CUSTOM_USER_AGENT: // Set the custom user agent. mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); break; default: - // Use the selected user agent. - mainWebView.getSettings().setUserAgentString(defaultUserAgentString); + // Get the user agent string from the user agent data array + mainWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); } - // Store the applied user agent string. + // Store the applied user agent string, which is used in the View Source activity. appliedUserAgentString = mainWebView.getSettings().getUserAgentString(); } @@ -3532,6 +3619,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (mainMenu != null) { updatePrivacyIcons(true); } + + // Reload the website if returning from the Domains activity. + if (reloadWebsite) { + mainWebView.reload(); + } } } @@ -3637,7 +3729,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void loadBookmarksFolder() { - // Update `bookmarksCursor` with the contents of the bookmarks database for the current folder. + // Update the bookmarks cursor with the contents of the bookmarks database for the current folder. bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder); // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`. @@ -3654,7 +3746,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon); TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name); - // Get the favorite icon byte array from the `Cursor`. + // Get the favorite icon byte array from the cursor. byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.