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=4905321bbae59a5d76c87c3b8fd721e54753fc23;hp=032aacb07109d24d164e906a9a6b8731448addb9;hb=0a5d2eabceeafb49a957598538aa74d4f11dfce0;hpb=1ec4db87420eb1b562b1869dca167277d5c02b42 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 032aacb0..4905321b 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -21,9 +21,11 @@ package com.stoutner.privacybrowser.activities; +import android.Manifest; import android.annotation.SuppressLint; import android.app.DialogFragment; import android.app.DownloadManager; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipboardManager; @@ -31,6 +33,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 +46,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 +56,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; @@ -83,6 +88,7 @@ import android.view.inputmethod.InputMethodManager; import android.webkit.CookieManager; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; +import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; import android.webkit.WebResourceResponse; @@ -90,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; @@ -101,20 +108,21 @@ 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.AddDomainDialog; +import com.stoutner.privacybrowser.dialogs.AdConsentDialog; 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; 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; @@ -139,12 +147,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. -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 { +// 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 CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, + CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, 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`, @@ -152,6 +160,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`. public static boolean darkTheme; + // `allowScreenshots` is public static so it can be accessed from everywhere. It is also used in `onCreate()`. + public static boolean allowScreenshots; + // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`, // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`, // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`. @@ -177,7 +188,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // `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()`. @@ -206,6 +217,14 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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; @@ -229,7 +248,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // `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()`. @@ -262,9 +281,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // `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; @@ -277,12 +293,15 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // The block list variables are used in `onCreate()` and `applyAppSettings()`. private boolean easyListEnabled; private boolean easyPrivacyEnabled; - private boolean fanboyAnnoyanceListEnabled; - private boolean fanboySocialBlockingListEnabled; + private boolean fanboysAnnoyanceListEnabled; + private boolean fanboysSocialBlockingListEnabled; // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`. private Runtime privacyBrowserRuntime; + // `proxyThroughOrbot` is used in `onRestart()` and `applyAppSettings()`. + private boolean proxyThroughOrbot; + // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`. private boolean incognitoModeEnabled; @@ -301,8 +320,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, . private boolean reapplyDomainSettingsOnRestart; - // `returnFromSettings` is used in `onNavigationItemSelected()` and `onRestart()`. - private boolean returnFromSettings; + // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`. + private boolean reapplyAppSettingsOnRestart; // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`. private String currentDomainName; @@ -351,9 +370,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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; @@ -390,17 +406,43 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`. private String oldFolderNameString; + // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`. + private ValueCallback fileChooserCallback; + + // 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 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; + @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) { - // Get a handle for `sharedPreferences`. `this` references the current context. + // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - // Get the theme preference. + // Get the theme and screenshot preferences. darkTheme = sharedPreferences.getBoolean("dark_theme", false); + allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } // Set the activity theme. if (darkTheme) { @@ -539,14 +581,14 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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(); @@ -566,9 +608,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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. @@ -603,11 +646,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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. @@ -729,7 +769,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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(); @@ -860,7 +900,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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); } } @@ -885,12 +925,13 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD webViewTitle = title; } - // Enter full screen video + // 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); + // 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. @@ -913,7 +954,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); } - // Exit full screen video + // Exit full screen video. + @Override public void onHideCustomView() { // Hide `fullScreenVideoFrameLayout`. fullScreenVideoFrameLayout.removeAllViews(); @@ -927,12 +969,26 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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)); + // 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)); + } + } + + // Upload files. + @Override + public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { + // Show the file chooser if the device is running API >= 21. + if (Build.VERSION.SDK_INT >= 21) { + // Store the file path callback. + fileChooserCallback = filePathCallback; + + // Create an intent to open a chooser based ont the file chooser parameters. + Intent fileChooserIntent = fileChooserParams.createIntent(); - // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. - adView = findViewById(R.id.adview); + // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`. + startActivityForResult(fileChooserIntent, 0); } + return true; } }); @@ -941,9 +997,33 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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)); + // 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. @@ -991,9 +1071,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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; @@ -1002,10 +1079,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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; @@ -1016,6 +1093,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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(); @@ -1036,46 +1117,68 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD mainWebView.setWebViewClient(new WebViewClient() { // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps. - // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24. + // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24. @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url.startsWith("mailto:")) { // Load the email address in an external email program. + if (url.startsWith("http")) { // Load the URL in Privacy Browser. + // Apply the domain settings for the new URL. + 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; + } else if (url.startsWith("mailto:")) { // Load the email address in an external email program. // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. Intent emailIntent = new Intent(Intent.ACTION_SENDTO); - // Parse the url and set it as the data for the `Intent`. + // Parse the url and set it as the data for the intent. emailIntent.setData(Uri.parse(url)); - // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser. + // Open the email program in a new task instead of as part of Privacy Browser. emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Make it so. startActivity(emailIntent); - // Returning `true` indicates the application is handling the URL. + // Returning true indicates Privacy Browser is handling the URL by creating an intent. return true; } else if (url.startsWith("tel:")) { // Load the phone number in the dialer. - // `ACTION_DIAL` open the dialer and loads the phone number, but waits for the user to place the call. + // Open the dialer and load the phone number, but wait for the user to place the call. Intent dialIntent = new Intent(Intent.ACTION_DIAL); // Add the phone number to the intent. dialIntent.setData(Uri.parse(url)); - // `FLAG_ACTIVITY_NEW_TASK` opens the dialer in a new task instead as part of Privacy Browser. + // Open the dialer in a new task instead of as part of Privacy Browser. dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Make it so. startActivity(dialIntent); - // Returning `true` indicates the application is handling the URL. + // Returning true indicates Privacy Browser is handling the URL by creating an intent. return true; - } else { // Load the URL in Privacy Browser. - // Apply the domain settings for the new URL. - applyDomainSettings(url); + } else { // Load a system chooser to select an app that can handle the URL. + // Open an app that can handle the URL. + Intent genericIntent = new Intent(Intent.ACTION_VIEW); - // Returning `false` causes the current `WebView` to handle the URL and prevents it from adding redirects to the history list. - return false; + // Add the URL to the intent. + genericIntent.setData(Uri.parse(url)); + + // List all apps that can handle the URL instead of just opening the first one. + genericIntent.addCategory(Intent.CATEGORY_BROWSABLE); + + // Open the app in a new task instead of as part of Privacy Browser. + genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Start the app or display a snackbar if no app is available to handle the URL. + try { + startActivity(genericIntent); + } catch (ActivityNotFoundException exception) { + Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show(); + } + + // Returning true indicates Privacy Browser is handling the URL by creating an intent. + return true; } } @@ -1103,12 +1206,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD } // Check Fanboy’s Annoyance List if it is enabled. - if (fanboyAnnoyanceListEnabled) { + if (fanboysAnnoyanceListEnabled) { if (blockListHelper.isBlocked(formattedUrlString, url, fanboyAnnoyance)) { // The resource request was blocked. Return an empty web resource response. return emptyWebResourceResponse; } - } else if (fanboySocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled. + } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled. if (blockListHelper.isBlocked(formattedUrlString, url, fanboySocial)) { // The resource request was blocked. Return an empty web resource response. return emptyWebResourceResponse; @@ -1153,7 +1256,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, 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. @@ -1182,11 +1285,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Manually delete cache folders. try { - // Delete the main `cache` folder. + // Delete the main cache directory. privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); - // Delete the `app_webview` folder, which contains an additional `WebView` cache. See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. - privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview"); + // Delete the secondary `Service Worker` cache directory. + // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. + privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); } catch (IOException e) { // Do nothing if an error is thrown. } @@ -1208,7 +1312,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, false); } else { // `WebView` has loaded a webpage. // Set `formattedUrlString`. formattedUrlString = url; @@ -1349,6 +1453,11 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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(); } @@ -1359,34 +1468,43 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Run the default commands. super.onRestart(); - // Apply the app settings if returning from the Settings activity.. - if (returnFromSettings) { - // Reset the return from settings flag. - returnFromSettings = false; + // Make sure Orbot is running if Privacy Browser is proxying through Orbot. + if (proxyThroughOrbot) { + // Request Orbot to start. If Orbot is already running no hard will be caused by this request. + Intent orbotIntent = new Intent("org.torproject.android.intent.action.START"); + // Send the intent to the Orbot package. + orbotIntent.setPackage("org.torproject.android"); + + // Make it so. + sendBroadcast(orbotIntent); + } + + // Apply the app settings if returning from the Settings activity.. + if (reapplyAppSettingsOnRestart) { // Apply the app settings. applyAppSettings(); - // Set the display webpage images mode. - setDisplayWebpageImages(); - } + // Reload the webpage if displaying of images has been disabled in the Settings activity. + if (reloadOnRestart) { + // Reload `mainWebView`. + mainWebView.reload(); - // Reload the webpage if displaying of images has been disabled in the Settings activity. - if (reloadOnRestart) { - // Reload `mainWebView`. - mainWebView.reload(); + // Reset `reloadOnRestartBoolean`. + reloadOnRestart = false; + } - // Reset `reloadOnRestartBoolean`. - reloadOnRestart = false; + // Reset the return from settings flag. + reapplyAppSettingsOnRestart = false; } // Apply the domain settings if returning from the Domains activity. if (reapplyDomainSettingsOnRestart) { + // Reapply the domain settings. + applyDomainSettings(formattedUrlString, false, true); + // Reset `reapplyDomainSettingsOnRestart`. reapplyDomainSettingsOnRestart = false; - - // Reapply the domain settings. - applyDomainSettings(formattedUrlString); } // Load the URL on restart to apply changes to night mode. @@ -1410,7 +1528,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD restartFromBookmarksActivity = false; } - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated. updatePrivacyIcons(true); } @@ -1428,7 +1546,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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)); } } @@ -1442,7 +1561,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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(); @@ -1498,8 +1618,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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) { @@ -1513,6 +1634,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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. @@ -1605,8 +1727,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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); @@ -1626,27 +1748,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // Set the commands that relate to the menu entries. switch (menuItemId) { - case R.id.add_or_edit_domain: - if (domainSettingsApplied) { // Edit the current domain settings. - // Reapply the domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - currentDomainName = ""; - - // 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. - domainsIntent.putExtra("LoadDomain", domainSettingsDatabaseId); - - // Make it so. - startActivity(domainsIntent); - } else { // Add a new domain. - // Show the add domain `AlertDialog`. - AppCompatDialogFragment addDomainDialog = new AddDomainDialog(); - addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain)); - } - return true; - case R.id.toggle_javascript: // Switch the status of javaScriptEnabled. javaScriptEnabled = !javaScriptEnabled; @@ -1670,6 +1771,48 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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`. + reapplyDomainSettingsOnRestart = true; + currentDomainName = ""; + + // 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 instead of returning to the domains list. + domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId); + domainsIntent.putExtra("closeOnBack", true); + + // Make it so. + startActivity(domainsIntent); + } else { // Add a new domain. + // Apply the new domain settings on returning to `MainWebViewActivity`. + reapplyDomainSettingsOnRestart = true; + currentDomainName = ""; + + // Get the current domain + Uri currentUri = Uri.parse(formattedUrlString); + String currentDomain = currentUri.getHost(); + + // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. + DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); + + // Create the domain and store the database ID. + int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain); + + // 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 new domain and close on back instead of returning to the domains list. + domainsIntent.putExtra("loadDomain", newDomainDatabaseId); + domainsIntent.putExtra("closeOnBack", true); + + // Make it so. + startActivity(domainsIntent); + } + return true; + case R.id.toggle_first_party_cookies: // Switch the status of firstPartyCookiesEnabled. firstPartyCookiesEnabled = !firstPartyCookiesEnabled; @@ -1686,7 +1829,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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(); @@ -1892,6 +2035,11 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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); @@ -1904,6 +2052,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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(); @@ -1950,12 +2104,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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(); @@ -1968,6 +2116,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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); @@ -2025,7 +2179,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 = ""; @@ -2035,13 +2189,13 @@ 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 = ""; - // Mark a flag to reapply app settings on restart only when returning from Settings. - returnFromSettings = true; - // Launch `SettingsActivity`. Intent settingsIntent = new Intent(this, SettingsActivity.class); startActivity(settingsIntent); @@ -2129,8 +2283,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD try { // Delete the main cache directory. privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache"); + // Delete the secondary `Service Worker` cache directory. - // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. + // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"}); } catch (IOException e) { // Do nothing if an error is thrown. @@ -2196,11 +2351,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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: @@ -2232,14 +2384,14 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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); @@ -2248,6 +2400,38 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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; @@ -2305,9 +2489,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; }); @@ -2342,9 +2548,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; }); @@ -2364,33 +2592,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD } } - @Override - public void onAddDomain(AppCompatDialogFragment dialogFragment) { - // Reapply the domain settings on returning to `MainWebViewActivity`. - reapplyDomainSettingsOnRestart = true; - currentDomainName = ""; - - // Get the new domain name `String` from `dialogFragment`. - EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext); - String domainNameString = domainNameEditText.getText().toString(); - - // 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`. - DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); - - // Create the domain and store the database ID in `currentDomainDatabaseId`. - int newDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString); - - // 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. - domainsIntent.putExtra("LoadDomain", newDomainDatabaseId); - - // Make it so. - startActivity(domainsIntent); - } - @Override public void onCreateBookmark(AppCompatDialogFragment dialogFragment) { // Get the `EditTexts` from the `dialogFragment`. @@ -2493,6 +2694,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. @@ -2513,15 +2766,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. @@ -2547,7 +2802,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); @@ -2564,15 +2818,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. @@ -2784,6 +3040,16 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD } } + // Process the results of an upload file chooser. Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored. + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // File uploads only work on API >= 21. + if (Build.VERSION.SDK_INT >= 21) { + // Pass the file to the WebView. + fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data)); + } + } + private void loadUrlFromTextBox() throws UnsupportedEncodingException { // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string. String unformattedUrlString = urlTextBox.getText().toString().trim(); @@ -2835,7 +3101,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD private void loadUrl(String url) { // Apply any custom domain settings. - applyDomainSettings(url); + applyDomainSettings(url, true, false); // Load the URL. mainWebView.loadUrl(url, customHeaders); @@ -2882,17 +3148,12 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD String torSearchCustomURLString = sharedPreferences.getString("tor_search_custom_url", ""); String searchString = sharedPreferences.getString("search", "https://duckduckgo.com/html/?q="); String searchCustomURLString = sharedPreferences.getString("search_custom_url", ""); - easyListEnabled = sharedPreferences.getBoolean("easylist", true); - easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true); - fanboyAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboy_annoyance_list", true); - fanboySocialBlockingListEnabled = sharedPreferences.getBoolean("fanboy_social_blocking_list", true); incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false); boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false); - boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false); + proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false); 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. @@ -2960,9 +3221,6 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD waitingForOrbot = false; } - // Set swipe to refresh. - swipeRefreshLayout.setEnabled(swipeToRefreshEnabled); - // Set Do Not Track status. if (doNotTrackEnabled) { customHeaders.put("DNT", "1"); @@ -3010,11 +3268,8 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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. @@ -3031,9 +3286,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD } } - // We have to use the deprecated `.getDrawable()` until the minimum API >= 21. + // `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) { + private void applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) { // Reset `navigatingHistory`. navigatingHistory = false; @@ -3063,9 +3319,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`. @@ -3120,8 +3378,9 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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. @@ -3136,10 +3395,15 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); saveFormDataEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1); - String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); + easyListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1); + 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 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)); @@ -3202,37 +3466,70 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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(); } @@ -3249,6 +3546,10 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false); domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false); saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); + easyListEnabled = sharedPreferences.getBoolean("easylist", true); + easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true); + fanboysAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboy_annoyance_list", true); + fanboysSocialBlockingListEnabled = sharedPreferences.getBoolean("fanboy_social_blocking_list", true); // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`. if (nightMode) { @@ -3261,6 +3562,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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; @@ -3282,23 +3584,32 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD // 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(); } @@ -3317,6 +3628,11 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD if (mainMenu != null) { updatePrivacyIcons(true); } + + // Reload the website if returning from the Domains activity. + if (reloadWebsite) { + mainWebView.reload(); + } } } @@ -3422,7 +3738,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD } 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`. @@ -3439,7 +3755,7 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD 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. @@ -3471,4 +3787,4 @@ public class MainWebViewActivity extends AppCompatActivity implements AddDomainD bookmarksTitleTextView.setText(currentBookmarksFolder); } } -} +} \ No newline at end of file