package com.stoutner.privacybrowser.activities;
+import android.Manifest;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.app.DialogFragment;
import android.app.DownloadManager;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipboardManager;
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;
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;
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;
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;
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;
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;
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 {
-
- // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`,
- // `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`,
- // `EditBookmarkDatabaseViewDialog`, `HttpAuthenticationDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`, `CreateHomeScreenShortcutDialog`,
- // and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
+// 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 everywhere.
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()`.
// and `ViewSslCertificateDialog`. It is also used in `onCreate()`.
public static SslCertificate sslCertificate;
- // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`.
+ // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()` and `onResume()`.
public static String orbotStatus;
// `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
// `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()`.
// The block list versions are public static so they can be accessed from `AboutTabFragment`. They are also used in `onCreate()`.
public static String easyListVersion;
public static String easyPrivacyVersion;
- public static String fanboyAnnoyanceVersion;
- public static String fanboySocialVersion;
+ public static String fanboysAnnoyanceVersion;
+ public static String fanboysSocialVersion;
+ public static String ultraPrivacyVersion;
+
+ // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`. They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
+ public static List<String[]> resourceRequests;
+ public static String[] whiteListResultStringArray;
+ private int blockedRequests;
+ private int easyListBlockedRequests;
+ private int easyPrivacyBlockedRequests;
+ private int fanboysAnnoyanceListBlockedRequests;
+ private int fanboysSocialBlockingListBlockedRequests;
+ private int ultraPrivacyBlockedRequests;
+ private int thirdPartyBlockedRequests;
+
+ public final static int REQUEST_DISPOSITION = 0;
+ public final static int REQUEST_URL = 1;
+ public final static int REQUEST_BLOCKLIST = 2;
+ public final static int REQUEST_SUBLIST = 3;
+ public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
+ public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
+
+ public final static int REQUEST_DEFAULT = 0;
+ public final static int REQUEST_ALLOWED = 1;
+ public final static int REQUEST_THIRD_PARTY = 2;
+ public final static int REQUEST_BLOCKED = 3;
+
+ public final static int MAIN_WHITELIST = 1;
+ public final static int FINAL_WHITELIST = 2;
+ public final static int DOMAIN_WHITELIST = 3;
+ public final static int DOMAIN_INITIAL_WHITELIST = 4;
+ public final static int DOMAIN_FINAL_WHITELIST = 5;
+ public final static int THIRD_PARTY_WHITELIST = 6;
+ public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
+ public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
+
+ public final static int MAIN_BLACKLIST = 9;
+ public final static int INITIAL_BLACKLIST = 10;
+ public final static int FINAL_BLACKLIST = 11;
+ public final static int DOMAIN_BLACKLIST = 12;
+ public final static int DOMAIN_INITIAL_BLACKLIST = 13;
+ public final static int DOMAIN_FINAL_BLACKLIST = 14;
+ public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
+ public final static int THIRD_PARTY_BLACKLIST = 16;
+ public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
+ public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
+ public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
+ public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
+ public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
+ public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
+
+ // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
+ // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
+ public static boolean blockAllThirdPartyRequests;
// `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
// `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
public static 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;
// `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
private FrameLayout fullScreenVideoFrameLayout;
- // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
+ // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
private SwipeRefreshLayout swipeRefreshLayout;
// `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
// `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
private boolean domStorageEnabled;
- // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
+ // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. It can be removed once the minimum API >= 26.
private boolean saveFormDataEnabled;
- // `nightMode` is used in `onCreate()` and `applyDomainSettings()`.
+ // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, 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;
// `searchURL` is used in `loadURLFromTextBox()` and `applyAppSettings()`.
private String searchURL;
- // The block list variables are used in `onCreate()` and `applyAppSettings()`.
+ // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
+ private Menu mainMenu;
+
+ // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
+ private MenuItem refreshMenuItem;
+
+ // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
+ private MenuItem blocklistsMenuItem;
+ private MenuItem easyListMenuItem;
+ private MenuItem easyPrivacyMenuItem;
+ private MenuItem fanboysAnnoyanceListMenuItem;
+ private MenuItem fanboysSocialBlockingListMenuItem;
+ private MenuItem ultraPrivacyMenuItem;
+ private MenuItem blockAllThirdPartyRequestsMenuItem;
+
+ // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
private boolean easyListEnabled;
private boolean easyPrivacyEnabled;
- private boolean fanboyAnnoyanceListEnabled;
- private boolean fanboySocialBlockingListEnabled;
+ private boolean fanboysAnnoyanceListEnabled;
+ private boolean fanboysSocialBlockingListEnabled;
+ private boolean ultraPrivacyEnabled;
+
+ // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
+ private String webViewDefaultUserAgent;
+
+ // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
+ private String defaultCustomUserAgentString;
// `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;
// `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
private boolean reapplyDomainSettingsOnRestart;
+ // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
+ private boolean reapplyAppSettingsOnRestart;
+
+ // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
+ private boolean displayingFullScreenVideo;
+
// `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
private String currentDomainName;
// `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
private boolean ignorePinnedSslCertificate;
- // `waitingForOrbot` is used in `onCreate()` and `applyAppSettings()`.
+ // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
+ private BroadcastReceiver orbotStatusBroadcastReceiver;
+
+ // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyAppSettings()`.
private boolean waitingForOrbot;
// `domainSettingsApplied` is used in `prepareOptionsMenu()`, `applyDomainSettings()`, and `setDisplayWebpageImages()`.
private boolean domainSettingsApplied;
+ // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
+ private Boolean domainSettingsJavaScriptEnabled;
+
// `displayWebpageImagesInt` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
private int displayWebpageImagesInt;
// `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
private EditText findOnPageEditText;
- // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
- private Menu mainMenu;
+ // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
+ private boolean displayAdditionalAppBarIcons;
// `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
private ActionBarDrawerToggle drawerToggle;
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;
// `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
private RelativeLayout mainWebViewRelativeLayout;
- // `urlIsLoading` is used in `onCreate()`, `loadUrl()`, and `applyDomainSettings()`.
+ // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`.
private boolean urlIsLoading;
// `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
private boolean pinnedDomainSslCertificate;
- // `bookmarksDatabaseHelper` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
+ // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
+ // and `loadBookmarksFolder()`.
private BookmarksDatabaseHelper bookmarksDatabaseHelper;
// `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
// `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
private TextView bookmarksTitleTextView;
- // `bookmarksCursor` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
+ // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
private Cursor bookmarksCursor;
// `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
// `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
private String oldFolderNameString;
+ // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
+ private ValueCallback<Uri[]> 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<CharSequence> 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) {
// Remove the formatting from `urlTextBar` when the user is editing the text.
urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
- if (hasFocus) { // The user is editing `urlTextBox`.
+ if (hasFocus) { // The user is editing the URL text box.
// Remove the highlighting.
urlTextBox.getText().removeSpan(redColorSpan);
urlTextBox.getText().removeSpan(initialGrayColorSpan);
urlTextBox.getText().removeSpan(finalGrayColorSpan);
- } else { // The user has stopped editing `urlTextBox`.
+ } else { // The user has stopped editing the URL text box.
+ // Move to the beginning of the string.
+ urlTextBox.setSelection(0);
+
// Reapply the highlighting.
highlightUrlText();
}
waitingForOrbot = false;
// Create an Orbot status `BroadcastReceiver`.
- BroadcastReceiver orbotStatusBroadcastReceiver = new BroadcastReceiver() {
+ orbotStatusBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Store the content of the status message in `orbotStatus`.
orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
- // If we are waiting on Orbot, load the website now that Orbot is connected.
+ // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
if (orbotStatus.equals("ON") && waitingForOrbot) {
// Reset `waitingForOrbot`.
waitingForOrbot = false;
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();
// 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.
// 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_unit_id));
}
// Remove the translucent navigation bar flag if it is set.
final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
+ final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
// Initialize the bookmarks database helper. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
// The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
// 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();
return true;
});
- // The `DrawerListener` allows us to update the Navigation Menu.
+ // The drawer listener is used to update the navigation menu.
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
@Override
public void onDrawerStateChanged(int newState) {
- if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // The drawer is opening or closing.
- // Update the `Back`, `Forward`, and `History` menu items.
+ if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
+ // Update the back, forward, history, and requests menu items.
navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
- // Hide the keyboard (if displayed) so we can see the navigation menu. `0` indicates no additional flags.
+ // Hide the keyboard (if displayed).
inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
- // Clear the focus from `urlTextBox` if it has it.
+ // Clear the focus from from the URL text box.
urlTextBox.clearFocus();
}
}
mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
"style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
"text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> {
- // Initialize a `Handler` to display `mainWebView`.
+ // Initialize a handler to display `mainWebView`.
Handler displayWebViewHandler = new Handler();
- // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied.
+ // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
Runnable displayWebViewRunnable = () -> {
// Only display `mainWebView` if the progress bar is one. This prevents the display of the `WebView` while it is still loading.
if (progressBar.getVisibility() == View.GONE) {
}
};
- // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds.
+ // Displaying of `mainWebView` after 500 milliseconds.
displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
});
}
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);
}
}
webViewTitle = title;
}
- // Enter full screen video
+ // Enter full screen video.
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
+ // Set the full screen video flag.
+ displayingFullScreenVideo = true;
+
// 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.
// Set `rootCoordinatorLayout` to fill the entire screen.
rootCoordinatorLayout.setFitsSystemWindows(false);
+ // Disable the sliding drawers.
+ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+
// Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
fullScreenVideoFrameLayout.addView(view);
fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
}
- // Exit full screen video
+ // Exit full screen video.
+ @Override
public void onHideCustomView() {
+ // Unset the full screen video flag.
+ displayingFullScreenVideo = false;
+
// Hide `fullScreenVideoFrameLayout`.
fullScreenVideoFrameLayout.removeAllViews();
fullScreenVideoFrameLayout.setVisibility(View.GONE);
- // Add the translucent status flag. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ // Enable the sliding drawers.
+ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
+
+ // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
+ if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
+ if (hideSystemBarsOnFullscreen) { // Hide everything.
+ // Remove the translucent navigation setting if it is currently flagged.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+
+ // Remove the translucent status bar overlay.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
+ drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+
+ /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ } else { // Hide everything except the status and navigation bars.
+ // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
+ rootCoordinatorLayout.setSystemUiVisibility(0);
+
+ // Add the translucent status flag if it is unset.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- // Set `rootCoordinatorLayout` to fit inside the status and navigation bars. This also clears the `SYSTEM_UI` flags.
- rootCoordinatorLayout.setFitsSystemWindows(true);
+ if (translucentNavigationBarOnFullscreen) {
+ // Set the navigation bar to be translucent. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ } else {
+ // Set the navigation bar to be black.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ }
+ }
+ } else { // Switch to normal viewing mode.
+ // Show the `appBar` if `findOnPageLinearLayout` is not visible.
+ if (findOnPageLinearLayout.getVisibility() == View.GONE) {
+ appBar.show();
+ }
+
+ // Show the `BannerAd` in the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // 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.google_app_id), getString(R.string.ad_unit_id));
+ }
+
+ // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
+ rootCoordinatorLayout.setSystemUiVisibility(0);
+
+ // Remove the translucent navigation bar flag if it is set.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+
+ // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
+ rootCoordinatorLayout.setFitsSystemWindows(true);
+ }
// Show the ad if this is the free flavor.
if (BuildConfig.FLAVOR.contentEquals("free")) {
- // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`.
- BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
+ // 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_unit_id));
+ }
+ }
- // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
- adView = findViewById(R.id.adview);
+ // Upload files.
+ @Override
+ public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> 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();
+
+ // 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;
}
});
// 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.
+ // Instantiate 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 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.
// Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
customHeaders.put("X-Requested-With", "");
- // Initialize the default preference values the first time the program is run. `this` is the context. `false` keeps this command from resetting any current preferences back to default.
+ // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
// Get the intent that started the app.
// Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
inFullScreenBrowsingMode = false;
- // Initialize AdView for the free flavor.
- adView = findViewById(R.id.adview);
-
// Initialize the privacy settings variables.
javaScriptEnabled = false;
firstPartyCookiesEnabled = false;
thirdPartyCookiesEnabled = false;
domStorageEnabled = false;
- saveFormDataEnabled = false;
+ saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26.
nightMode = false;
- // Initialize `webViewTitle`.
+ // Store the default user agent.
+ webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
+
+ // 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;
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();
// Instantiate the block list helper.
BlockListHelper blockListHelper = new BlockListHelper();
+ // Initialize the list of resource requests.
+ resourceRequests = new ArrayList<>();
+
// Parse the block lists.
final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
- final ArrayList<List<String[]>> fanboyAnnoyance = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
- final ArrayList<List<String[]>> fanboySocial = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
+ final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
+ final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
+ final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
// Store the list versions.
easyListVersion = easyList.get(0).get(0)[0];
easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
- fanboyAnnoyanceVersion = fanboyAnnoyance.get(0).get(0)[0];
- fanboySocialVersion = fanboySocial.get(0).get(0)[0];
+ fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
+ fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
+ ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
+
+ // Get a handle for the activity. This is used to update the requests counter while the navigation menu is open.
+ Activity activity = this;
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.
+ // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+ formattedUrlString = "";
+
+ // Apply the domain settings for the new URL. `applyDomainSettings` doesn't do anything if the domain has not changed.
+ 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;
}
}
- // Check requests against the block lists. The deprecated `shouldInterceptRequest` must be used until minimum API >= 21.
+ // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
@SuppressWarnings("deprecation")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url){
// Create an empty web resource response to be used if the resource request is blocked.
WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
+ // Reset the whitelist results tracker.
+ whiteListResultStringArray = null;
+
+ // Initialize the third party request tracker.
+ boolean isThirdPartyRequest = false;
+
+ // Initialize the current domain string.
+ String currentDomain = "";
+
+ // Nobody is happy when comparing null strings.
+ if (!(formattedUrlString == null) && !(url == null)) {
+ // Get the domain strings to URIs.
+ Uri currentDomainUri = Uri.parse(formattedUrlString);
+ Uri requestDomainUri = Uri.parse(url);
+
+ // Get the domain host names.
+ String currentBaseDomain = currentDomainUri.getHost();
+ String requestBaseDomain = requestDomainUri.getHost();
+
+ // Update the current domain variable.
+ currentDomain = currentBaseDomain;
+
+ // Only compare the current base domain and the request base domain if neither is null.
+ if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
+ // Determine the current base domain.
+ while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
+ // Remove the first subdomain.
+ currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
+ }
+
+ // Determine the request base domain.
+ while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
+ // Remove the first subdomain.
+ requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
+ }
+
+ // Update the third party request tracker.
+ isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
+ }
+ }
+
+ // Block third-party requests if enabled.
+ if (isThirdPartyRequest && blockAllThirdPartyRequests) {
+ // Increment the blocked requests counters.
+ blockedRequests++;
+ thirdPartyBlockedRequests++;
+
+ // Update the titles of the blocklist menu items. This must be run from the UI thread.
+ activity.runOnUiThread(() -> {
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
+ });
+
+ // Add the request to the log.
+ resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
+
+ // Return an empty web resource response.
+ return emptyWebResourceResponse;
+ }
+
+ // Check UltraPrivacy if it is enabled.
+ if (ultraPrivacyEnabled) {
+ if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
+ // Increment the blocked requests counters.
+ blockedRequests++;
+ ultraPrivacyBlockedRequests++;
+
+ // Update the titles of the blocklist menu items. This must be run from the UI thread.
+ activity.runOnUiThread(() -> {
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
+ });
+
+ // The resource request was blocked. Return an empty web resource response.
+ return emptyWebResourceResponse;
+ }
+
+ // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
+ if (whiteListResultStringArray != null) {
+ // Add a whitelist entry to the resource requests array.
+ resourceRequests.add(whiteListResultStringArray);
+
+ // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
+ return null;
+ }
+ }
+
// Check EasyList if it is enabled.
if (easyListEnabled) {
- if (blockListHelper.isBlocked(formattedUrlString, url, easyList)) {
+ if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
+ // Increment the blocked requests counters.
+ blockedRequests++;
+ easyListBlockedRequests++;
+
+ // Update the titles of the blocklist menu items. This must be run from the UI thread.
+ activity.runOnUiThread(() -> {
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
+ });
+
+ // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
+ whiteListResultStringArray = null;
+
// The resource request was blocked. Return an empty web resource response.
return emptyWebResourceResponse;
}
// Check EasyPrivacy if it is enabled.
if (easyPrivacyEnabled) {
- if (blockListHelper.isBlocked(formattedUrlString, url, easyPrivacy)) {
+ if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
+ // Increment the blocked requests counters.
+ blockedRequests++;
+ easyPrivacyBlockedRequests++;
+
+ // Update the titles of the blocklist menu items. This must be run from the UI thread.
+ activity.runOnUiThread(() -> {
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
+ });
+
+ // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
+ whiteListResultStringArray = null;
+
// The resource request was blocked. Return an empty web resource response.
return emptyWebResourceResponse;
}
}
// Check Fanboy’s Annoyance List if it is enabled.
- if (fanboyAnnoyanceListEnabled) {
- if (blockListHelper.isBlocked(formattedUrlString, url, fanboyAnnoyance)) {
+ if (fanboysAnnoyanceListEnabled) {
+ if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
+ // Increment the blocked requests counters.
+ blockedRequests++;
+ fanboysAnnoyanceListBlockedRequests++;
+
+ // Update the titles of the blocklist menu items. This must be run from the UI thread.
+ activity.runOnUiThread(() -> {
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
+ });
+
+ // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
+ whiteListResultStringArray = null;
+
// 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.
- if (blockListHelper.isBlocked(formattedUrlString, url, fanboySocial)) {
+ } else if (fanboysSocialBlockingListEnabled){ // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
+ if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
+ // Increment the blocked requests counters.
+ blockedRequests++;
+ fanboysSocialBlockingListBlockedRequests++;
+
+ // Update the titles of the blocklist menu items. This must be run from the UI thread.
+ activity.runOnUiThread(() -> {
+ navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
+ fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
+ });
+
+ // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
+ whiteListResultStringArray = null;
+
// The resource request was blocked. Return an empty web resource response.
return emptyWebResourceResponse;
}
}
+ // Add the request to the log because it hasn't been processed by any of the previous checks.
+ if (whiteListResultStringArray != null ) { // The request was processed by a whitelist.
+ resourceRequests.add(whiteListResultStringArray);
+ } else { // The request didn't match any blocklist entry. Log it as a default request.
+ resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
+ }
+
// The resource request has not been blocked. `return null` loads the requested resource.
return null;
}
// Update the URL in urlTextBox when the page starts to load.
@Override
- public void onPageStarted(WebView view, String url, Bitmap favicon) {// If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ // Reset the list of resource requests.
+ resourceRequests.clear();
+
+ // Initialize the counters for requests blocked by each blocklist.
+ blockedRequests = 0;
+ easyListBlockedRequests = 0;
+ easyPrivacyBlockedRequests = 0;
+ fanboysAnnoyanceListBlockedRequests = 0;
+ fanboysSocialBlockingListBlockedRequests = 0;
+ ultraPrivacyBlockedRequests = 0;
+ thirdPartyBlockedRequests = 0;
+
+ // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
if (nightMode) {
mainWebView.setVisibility(View.INVISIBLE);
}
// Hide the keyboard. `0` indicates no additional flags.
inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
- // Check to see if we are waiting on Orbot.
+ // Check to see if Privacy Browser is waiting on Orbot.
if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
// We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
formattedUrlString = url;
// Apply any custom domain settings if the URL was loaded by navigating history.
if (navigatingHistory) {
- applyDomainSettings(url);
+ // Reset `navigatingHistory`.
+ navigatingHistory = false;
+
+ // Apply the domain settings.
+ 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.
urlIsLoading = true;
+
+ // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
+ if (refreshMenuItem != null) {
+ // Set the title.
+ refreshMenuItem.setTitle(R.string.stop);
+
+ // If the icon is displayed in the AppBar, set it according to the theme.
+ if (displayAdditionalAppBarIcons) {
+ if (darkTheme) {
+ refreshMenuItem.setIcon(R.drawable.close_dark);
+ } else {
+ refreshMenuItem.setIcon(R.drawable.close_light);
+ }
+ }
+ }
}
}
// It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
@Override
public void onPageFinished(WebView view, String url) {
+ // Reset the wide view port if it has been turned off by the waiting for Orbot message.
+ if (!waitingForOrbot) {
+ mainWebView.getSettings().setUseWideViewPort(true);
+ }
+
// Flush any cookies to persistent storage. `CookieManager` has become very lazy about flushing cookies in recent versions.
- if (Build.VERSION.SDK_INT >= 21) {
+ if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
cookieManager.flush();
}
+ // Update the Refresh menu item if it has been created.
+ if (refreshMenuItem != null) {
+ // Reset the Refresh title.
+ refreshMenuItem.setTitle(R.string.refresh);
+
+ // If the icon is displayed in the AppBar, reset it according to the theme.
+ if (displayAdditionalAppBarIcons) {
+ if (darkTheme) {
+ refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
+ } else {
+ refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
+ }
+ }
+ }
+
// Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
urlIsLoading = false;
// 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.
}
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;
// Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
setIntent(intent);
+ // Check to see if the intent contains a new URL.
if (intent.getData() != null) {
- // Get the intent data and convert it to a string.
+ // Get the intent data.
final Uri intentUriData = intent.getData();
- formattedUrlString = intentUriData.toString();
- }
- // Close the navigation drawer if it is open.
- if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
- drawerLayout.closeDrawer(GravityCompat.START);
- }
+ // Load the website.
+ loadUrl(intentUriData.toString());
- // Load the website.
- loadUrl(formattedUrlString);
+ // Close the navigation drawer if it is open.
+ if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
+ drawerLayout.closeDrawer(GravityCompat.START);
+ }
- // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
- mainWebView.requestFocus();
+ // 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();
+ }
}
@Override
// Run the default commands.
super.onRestart();
- // Apply the app settings, which may have been changed in `SettingsActivity`.
- applyAppSettings();
+ // 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");
- // Apply the domain settings if returning from the Domains Activity.
- if (reapplyDomainSettingsOnRestart) {
- // Reset `reapplyDomainSettingsOnRestart`.
- reapplyDomainSettingsOnRestart = false;
+ // Send the intent to the Orbot package.
+ orbotIntent.setPackage("org.torproject.android");
- // Reapply the domain settings.
- applyDomainSettings(formattedUrlString);
+ // Make it so.
+ sendBroadcast(orbotIntent);
}
- // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
- updatePrivacyIcons(true);
+ // 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 `SettingsFragment`.
- if (reloadOnRestart) {
- // Reload `mainWebView`.
- mainWebView.reload();
+ // 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 `reloadOnRestartBoolean`.
- reloadOnRestart = false;
+ // Reset `reapplyDomainSettingsOnRestart`.
+ reapplyDomainSettingsOnRestart = false;
}
// Load the URL on restart to apply changes to night mode.
loadUrlOnRestart = false;
}
- //
+ // Update the bookmarks drawer if returning from the Bookmarks activity.
if (restartFromBookmarksActivity) {
// Close the bookmarks drawer.
drawerLayout.closeDrawer(GravityCompat.END);
// Reset `restartFromBookmarksActivity`.
restartFromBookmarksActivity = false;
}
+
+ // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
+ updatePrivacyIcons(true);
}
// `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
// Resume the adView for the free flavor.
if (BuildConfig.FLAVOR.contentEquals("free")) {
- BannerAd.resumeAd(adView);
+ // Resume the ad.
+ AdHelper.resumeAd(findViewById(R.id.adview));
+ }
+
+ // Display a message to the user if waiting for Orbot.
+ if (waitingForOrbot && !orbotStatus.equals("ON")) {
+ // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
+ mainWebView.getSettings().setUseWideViewPort(false);
+
+ // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
+ mainWebView.loadData(waitingForOrbotHTMLString, "text/html", null);
+ }
+
+ if (displayingFullScreenVideo) {
+ // Remove the translucent overlays.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
+ drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+
+ /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
@Override
public void onPause() {
+ // Run the default commands.
+ super.onPause();
+
// Pause `mainWebView`.
mainWebView.onPause();
// Stop all JavaScript.
mainWebView.pauseTimers();
- // Pause the adView or it will continue to consume resources in the background on the free flavor.
+ // Pause the ad or it will continue to consume resources in the background on the free flavor.
if (BuildConfig.FLAVOR.contentEquals("free")) {
- BannerAd.pauseAd(adView);
+ // Pause the ad.
+ AdHelper.pauseAd(findViewById(R.id.adview));
}
+ }
- super.onPause();
+ @Override
+ public void onDestroy() {
+ // Unregister the Orbot status broadcast receiver.
+ this.unregisterReceiver(orbotStatusBroadcastReceiver);
+
+ // Close the bookmarks cursor and database.
+ bookmarksCursor.close();
+ bookmarksDatabaseHelper.close();
+
+ // Run the default commands.
+ super.onDestroy();
}
@Override
MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
- MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);
-
- // Only display third-party cookies if SDK >= 21
+ MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
+ MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
+ refreshMenuItem = menu.findItem(R.id.refresh);
+ blocklistsMenuItem = menu.findItem(R.id.blocklists);
+ easyListMenuItem = menu.findItem(R.id.easylist);
+ easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
+ fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
+ fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
+ ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
+ blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
+ MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
+
+ // Only display third-party cookies if API >= 21
toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
- // Get the shared preference values. `this` references the current context.
+ // Only display the form data menu items if the API < 26.
+ toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+ clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
+
+ // Only show Ad Consent if this is the free flavor.
+ adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
+
+ // Get the shared preference values.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- // Set the status of the additional app bar icons. The default is `false`.
- if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
+ // Get the status of the additional AppBar icons.
+ displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+
+ // Set the status of the additional app bar icons. Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
+ if (displayAdditionalAppBarIcons) {
toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- toggleSaveFormDataMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
} else { //Do not display the additional icons.
toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- toggleSaveFormDataMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ }
+
+ // Replace Refresh with Stop if a URL is already loading.
+ if (urlIsLoading) {
+ // Set the title.
+ refreshMenuItem.setTitle(R.string.stop);
+
+ // If the icon is displayed in the AppBar, set it according to the theme.
+ if (displayAdditionalAppBarIcons) {
+ if (darkTheme) {
+ refreshMenuItem.setIcon(R.drawable.close_dark);
+ } else {
+ refreshMenuItem.setIcon(R.drawable.close_light);
+ }
+ }
}
return true;
MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
- MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);
+ MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
- MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);
+ MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
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 nightModeMenuItem = menu.findItem(R.id.night_mode);
// Set the text for the domain menu item.
if (domainSettingsApplied) {
toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
toggleDomStorageMenuItem.setChecked(domStorageEnabled);
- toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled);
+ toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled); // Form data can be removed once the minimum API >= 26.
+ easyListMenuItem.setChecked(easyListEnabled);
+ easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
+ fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
+ fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
+ ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
+ blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
+ swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
+ nightModeMenuItem.setChecked(nightMode);
// Enable third-party cookies if first-party cookies are enabled.
toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
- // Enable `DOM Storage` if JavaScript is enabled.
+ // Enable DOM Storage if JavaScript is enabled.
toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
- // Enable `Clear Cookies` if there are any.
+ // Enable Clear Cookies if there are any.
clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
- // Get a count of the number of files in the `Local Storage` directory.
+ // Get a count of the number of files in the Local Storage directory.
File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
int localStorageDirectoryNumberOfFiles = 0;
if (localStorageDirectory.exists()) {
localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
}
- // Get a count of the number of files in the `IndexedDB` directory.
+ // Get a count of the number of files in the IndexedDB directory.
File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
int indexedDBDirectoryNumberOfFiles = 0;
if (indexedDBDirectory.exists()) {
indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
}
- // Enable `Clear DOM Storage` if there is any.
+ // Enable Clear DOM Storage if there is any.
clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
- // Enable `Clear Form Data` is there is any.
- WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
- clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
+ // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
+ if (Build.VERSION.SDK_INT < 26) {
+ WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
+ clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
+ } else {
+ // Disable clear form data because it is not supported on current version of Android.
+ clearFormDataMenuItem.setEnabled(false);
+ }
- // Enable `Clear Data` if any of the submenu items are enabled.
+ // Enable Clear Data if any of the submenu items are enabled.
clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
+ // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
+ fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
+
+ // Initialize the display names for the blocklists with the number of blocked requests.
+ blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
+ easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
+ easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
+ fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
+ fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
+ ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
+ blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
+
+ // Get the current user agent.
+ String currentUserAgent = mainWebView.getSettings().getUserAgentString();
+
+ // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
+ if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
+ menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
+ } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
+ menu.findItem(R.id.user_agent_webview_default).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
+ menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
+ menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
+ menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
+ menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
+ menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
+ menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
+ menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
+ menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
+ menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
+ } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
+ menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
+ } else { // Custom user agent.
+ menu.findItem(R.id.user_agent_custom).setChecked(true);
+ }
+
// Initialize font size variables.
int fontSize = mainWebView.getSettings().getTextZoom();
String fontSizeTitle;
fontSizeMenuItem.setTitle(fontSizeTitle);
selectedFontSizeMenuItem.setChecked(true);
- // Only show `Refresh` if `swipeToRefresh` is disabled.
- refreshMenuItem.setVisible(!swipeToRefreshEnabled);
-
// Run all the other default commands.
super.onPrepareOptionsMenu(menu);
// 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;
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;
// 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();
mainWebView.reload();
return true;
+ // Form data can be removed once the minimum API >= 26.
case R.id.toggle_save_form_data:
// Switch the status of saveFormDataEnabled.
saveFormDataEnabled = !saveFormDataEnabled;
WebStorage webStorage = WebStorage.getInstance();
webStorage.deleteAllData();
- // Manually delete the DOM storage files and directories.
- try {
- // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
- privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
-
- // Multiple commands must be used because `Runtime.exec()` does not like `*`.
- privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
- privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
- privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
- privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
- } catch (IOException e) {
- // Do nothing if an error is thrown.
- }
+ // Initialize a handler to manually delete the DOM storage files and directories.
+ Handler deleteDomStorageHandler = new Handler();
+
+ // Setup a runnable to manually delete the DOM storage files and directories.
+ Runnable deleteDomStorageRunnable = () -> {
+ try {
+ // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+ privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+ // Multiple commands must be used because `Runtime.exec()` does not like `*`.
+ privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+ privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+ privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+ privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+ } catch (IOException e) {
+ // Do nothing if an error is thrown.
+ }
+ };
+
+ // Manually delete the DOM storage files after 200 milliseconds.
+ deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
}
}
})
.show();
return true;
+ // Form data can be remove once the minimum API >= 26.
case R.id.clear_form_data:
Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
.setAction(R.string.undo, v -> {
.show();
return true;
+ case R.id.easylist:
+ // Toggle the EasyList status.
+ easyListEnabled = !easyListEnabled;
+
+ // Update the menu checkbox.
+ menuItem.setChecked(easyListEnabled);
+
+ // Reload the main WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.easyprivacy:
+ // Toggle the EasyPrivacy status.
+ easyPrivacyEnabled = !easyPrivacyEnabled;
+
+ // Update the menu checkbox.
+ menuItem.setChecked(easyPrivacyEnabled);
+
+ // Reload the main WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.fanboys_annoyance_list:
+ // Toggle Fanboy's Annoyance List status.
+ fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
+
+ // Update the menu checkbox.
+ menuItem.setChecked(fanboysAnnoyanceListEnabled);
+
+ // Update the staus of Fanboy's Social Blocking List.
+ MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
+ fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
+
+ // Reload the main WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.fanboys_social_blocking_list:
+ // Toggle Fanboy's Social Blocking List status.
+ fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
+
+ // Update the menu checkbox.
+ menuItem.setChecked(fanboysSocialBlockingListEnabled);
+
+ // Reload the main WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.ultraprivacy:
+ // Toggle the UltraPrivacy status.
+ ultraPrivacyEnabled = !ultraPrivacyEnabled;
+
+ // Update the menu checkbox.
+ menuItem.setChecked(ultraPrivacyEnabled);
+
+ // Reload the main WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.block_all_third_party_requests:
+ //Toggle the third-party requests blocker status.
+ blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
+
+ // Update the menu checkbox.
+ menuItem.setChecked(blockAllThirdPartyRequests);
+
+ // Reload the main WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_privacy_browser:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_webview_default:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString("");
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_firefox_on_android:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_chrome_on_android:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_safari_on_ios:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_firefox_on_linux:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_chromium_on_linux:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_firefox_on_windows:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_chrome_on_windows:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_edge_on_windows:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_internet_explorer_on_windows:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_safari_on_macos:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
+ case R.id.user_agent_custom:
+ // Update the user agent.
+ mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+
+ // Reload the WebView.
+ mainWebView.reload();
+ return true;
+
case R.id.font_size_twenty_five_percent:
mainWebView.getSettings().setTextZoom(25);
return true;
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);
onTheFlyDisplayImagesSet = true;
return true;
+ case R.id.night_mode:
+ // Toggle night mode.
+ nightMode = !nightMode;
+
+ // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
+ if (nightMode) { // Night mode is enabled. Enable JavaScript.
+ // Update the global variable.
+ javaScriptEnabled = true;
+ } else if (domainSettingsApplied) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
+ // Get the JavaScript preference that was stored the last time domain settings were loaded.
+ javaScriptEnabled = domainSettingsJavaScriptEnabled;
+ } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
+ // Get a handle for the shared preference.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // Get the JavaScript preference.
+ javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
+ }
+
+ // Apply the JavaScript setting to the WebView.
+ mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
+
+ // Update the privacy icons.
+ updatePrivacyIcons(false);
+
+ // Reload the website.
+ mainWebView.reload();
+ 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();
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();
return true;
case R.id.refresh:
- mainWebView.reload();
+ if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
+ // Reload the WebView.
+ mainWebView.reload();
+ } else { // The stop button was pushed.
+ // Stop the loading of the WebView.
+ mainWebView.stopLoading();
+ }
+ 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:
case R.id.back:
if (mainWebView.canGoBack()) {
+ // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+ formattedUrlString = "";
+
// Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
navigatingHistory = true;
case R.id.forward:
if (mainWebView.canGoForward()) {
+ // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+ formattedUrlString = "";
+
// Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
navigatingHistory = true;
urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
break;
+ case R.id.requests:
+ // Launch the requests activity.
+ Intent requestsIntent = new Intent(this, RequestsActivity.class);
+ startActivity(requestsIntent);
+ break;
+
case R.id.downloads:
// Launch the system Download Manager.
Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
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 = "";
- // Launch `DomainsActivity`.
+ // Launch the domains activity.
Intent domainsIntent = new Intent(this, DomainsActivity.class);
startActivity(domainsIntent);
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 = "";
- // Launch `SettingsActivity`.
+ // Launch the settings activity.
Intent settingsIntent = new Intent(this, SettingsActivity.class);
startActivity(settingsIntent);
break;
+ case R.id.import_export:
+ // Launch the import/export activity.
+ Intent importExportIntent = new Intent (this, ImportExportActivity.class);
+ startActivity(importExportIntent);
+ break;
+
case R.id.guide:
// Launch `GuideActivity`.
Intent guideIntent = new Intent(this, GuideActivity.class);
break;
case R.id.clearAndExit:
+ // Close the bookmarks cursor and database.
+ bookmarksCursor.close();
+ bookmarksDatabaseHelper.close();
+
// Get a handle for `sharedPreferences`. `this` references the current context.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
}
}
- // Clear form data.
- if (clearEverything || sharedPreferences.getBoolean("clear_form_data", true)) {
+ // Clear form data if the API < 26.
+ if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
webViewDatabase.clearFormData();
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.
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- // Reload the ad for the free flavor if we are not in full screen mode.
+ // Reload the ad for the free flavor if we 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_unit_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:
// 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);
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.
+ // Instantiate 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;
// 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.
+ // Instantiate 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;
});
// 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.
+ // Instantiate 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;
});
}
}
- @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`.
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.
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.
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);
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.
@Override
public void onSslMismatchBack() {
if (mainWebView.canGoBack()) { // There is a back page in the history.
+ // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+ formattedUrlString = "";
+
// Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
navigatingHistory = true;
@Override
public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
+ // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+ formattedUrlString = "";
+
// Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
navigatingHistory = true;
}
} else if (mainWebView.canGoBack()) { // There is at least one item in the `WebView` history.
+ // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
+ formattedUrlString = "";
+
// Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
navigatingHistory = true;
}
}
+ // 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();
// Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
if ((Patterns.WEB_URL.matcher(unformattedUrlString).matches()) || (unformattedUrlString.startsWith("http://")) || (unformattedUrlString.startsWith("https://"))) {
- // Add `http://` at the beginning if it is missing. Otherwise the app will segfault.
+ // Add `https://` at the beginning if it is missing. Otherwise the app will segfault.
if (!unformattedUrlString.startsWith("http")) {
- unformattedUrlString = "http://" + unformattedUrlString;
+ unformattedUrlString = "https://" + unformattedUrlString;
}
// Initialize `unformattedUrl`.
// Decode `formattedUri` as a `String` in `UTF-8`.
formattedUrlString = URLDecoder.decode(formattedUri.build().toString(), "UTF-8");
- } else {
+ } else if (unformattedUrlString.isEmpty()){ // Load a blank web site.
+ // Load a blank string.
+ formattedUrlString = "";
+ } else { // Search for the contents of the URL box.
// Sanitize the search input and convert it to a search.
final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
// Clear the focus from the URL text box. Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
urlTextBox.clearFocus();
+ // Make it so.
loadUrl(formattedUrlString);
}
+ private void loadUrl(String url) {// Apply any custom domain settings.
+ // Set the URL as the formatted URL string so that checking third-party requests works correctly.
+ formattedUrlString = url;
- private void loadUrl(String url) {
- // Apply any custom domain settings.
- applyDomainSettings(url);
+ // Apply the domain settings.
+ applyDomainSettings(url, true, false);
+
+ // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
+ urlIsLoading = !url.equals("");
// Load the URL.
mainWebView.loadUrl(url, customHeaders);
-
- // Set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
- urlIsLoading = true;
}
public void findPreviousOnPage(View view) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
// Store the values from the shared preferences in variables.
- String homepageString = sharedPreferences.getString("homepage", "https://start.duckduckgo.com");
- String torHomepageString = sharedPreferences.getString("tor_homepage", "https://3g2upl4pq6kufc4m.onion");
- String torSearchString = sharedPreferences.getString("tor_search", "https://3g2upl4pq6kufc4m.onion/html/?q=");
+ String homepageString = sharedPreferences.getString("homepage", "https://searx.me/");
+ String torHomepageString = sharedPreferences.getString("tor_homepage", "http://ulrn6sryqaifefld.onion/");
+ String torSearchString = sharedPreferences.getString("tor_search", "http://ulrn6sryqaifefld.onion/?q=");
String torSearchCustomURLString = sharedPreferences.getString("tor_search_custom_url", "");
- String searchString = sharedPreferences.getString("search", "https://duckduckgo.com/html/?q=");
+ String searchString = sharedPreferences.getString("search", "https://searx.me/?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.
appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
}
- // Display a message to the user if we are waiting on Orbot.
+ // Display a message to the user if waiting for Orbot.
if (!orbotStatus.equals("ON")) {
// Set `waitingForOrbot`.
waitingForOrbot = true;
+ // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
+ mainWebView.getSettings().setUseWideViewPort(false);
+
// Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
mainWebView.loadData(waitingForOrbotHTMLString, "text/html", null);
}
waitingForOrbot = false;
}
- // Set swipe to refresh.
- swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
-
// Set Do Not Track status.
if (doNotTrackEnabled) {
customHeaders.put("DNT", "1");
}
// Apply the appropriate full screen mode the `SYSTEM_UI` flags.
- if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {
+ if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
if (hideSystemBarsOnFullscreen) { // Hide everything.
// Remove the translucent navigation setting if it is currently flagged.
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
*/
rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
} else { // Hide everything except the status and navigation bars.
+ // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
+ rootCoordinatorLayout.setSystemUiVisibility(0);
+
// Add the translucent status flag if it is unset.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
}
- } else { // Switch to normal viewing mode.
- // Reset `inFullScreenBrowsingMode` to `false`.
+ } else { // Privacy Browser is not in full screen browsing mode.
+ // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
inFullScreenBrowsingMode = false;
// Show the `appBar` if `findOnPageLinearLayout` is not visible.
// 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.google_app_id), getString(R.string.ad_unit_id));
}
+ // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
+ rootCoordinatorLayout.setSystemUiVisibility(0);
+
// Remove the translucent navigation bar flag if it is set.
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
// Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
- rootCoordinatorLayout.setSystemUiVisibility(0);
-
// Constrain `rootCoordinatorLayout` inside the status and navigation bars.
rootCoordinatorLayout.setFitsSystemWindows(true);
}
}
- // 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) {
- // Reset `navigatingHistory`.
- navigatingHistory = false;
-
+ private void applyDomainSettings(String url, boolean resetFavoriteIcon, boolean reloadWebsite) {
// Parse the URL into a URI.
Uri uri = Uri.parse(url);
loadingNewDomainName = !hostName.equals(currentDomainName);
}
+ // Strings don't like to be null.
+ if (hostName == null) {
+ hostName = "";
+ }
+
// Only apply the domain settings if a new domain is being loaded. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
if (loadingNewDomainName) {
// Set the new `hostname` as the `currentDomainName`.
// 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`.
+ // 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);
// Get a full cursor from `domainsDatabaseHelper`.
domainNameInDatabase = hostName;
}
- // If `hostName` is not `null`, check all the subdomains of `hostName` against wildcard domains in `domainCursor`.
- if (hostName != null) {
- while (hostName.contains(".") && !domainSettingsApplied) { // Stop checking if we run out of `.` or if we already know that `domainSettingsApplied` is `true`.
- if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
- domainSettingsApplied = true;
- domainNameInDatabase = "*." + hostName;
- }
+ // Check all the subdomains of the host name against wildcard domains in the domain cursor.
+ while (!domainSettingsApplied && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
+ if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`.
+ // Apply the domain settings.
+ domainSettingsApplied = true;
- // Strip out the lowest subdomain of `host`.
- hostName = hostName.substring(hostName.indexOf(".") + 1);
+ // Store the applied domain names as it appears in the database.
+ domainNameInDatabase = "*." + hostName;
}
+
+ // Strip out the lowest subdomain of of the host name.
+ hostName = hostName.substring(hostName.indexOf(".") + 1);
}
- // Get a handle for the shared preference. `this` references the current context.
+
+ // Get a handle for the shared preference.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
// Store the general preference information.
String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
- String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
- String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
+ String defaultUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser");
+ 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.
firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
+ // Form data can be removed once the minimum API >= 26.
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);
+ ultraPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
+ blockAllThirdPartyRequests = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 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));
break;
}
- // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
+ // Store the domain JavaScript status. This is used by the options menu night mode toggle.
+ domainSettingsJavaScriptEnabled = javaScriptEnabled;
+
+ // Enable JavaScript if night mode is enabled.
if (nightMode) {
javaScriptEnabled = true;
}
mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
- mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
+
+ // Apply the form data setting if the API < 26.
+ if (Build.VERSION.SDK_INT < 26) {
+ mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
+ }
// Apply the font size.
if (fontSize == 0) { // Apply the default font size.
// 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.
// <https://redmine.stoutner.com/issues/160>
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();
}
} else {
urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
}
- } else { // The URL we are loading does not have custom domain settings. Load the defaults.
+ } else { // The new URL does not have custom domain settings. Load the defaults.
// Store the values from `sharedPreferences` in variables.
javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
- saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
+ saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); // Form data can be removed once the minimum API >= 26.
+ 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);
+ ultraPrivacyEnabled = sharedPreferences.getBoolean("ultraprivacy", true);
+ blockAllThirdPartyRequests = sharedPreferences.getBoolean("block_all_third_party_requests", false);
// Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
if (nightMode) {
mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
- mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
+ swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
+
+ // Apply the form data setting if the API < 26.
+ if (Build.VERSION.SDK_INT < 26) {
+ mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
+ }
// Reset the pinned SSL certificate information.
domainSettingsDatabaseId = -1;
// 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.
// <https://redmine.stoutner.com/issues/160>
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();
}
urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
}
- // Close `domainsDatabaseHelper`.
+ // Close the domains database helper.
domainsDatabaseHelper.close();
// Remove the `onTheFlyDisplayImagesSet` flag and set the display webpage images mode. `true` indicates that custom domain settings are applied.
updatePrivacyIcons(true);
}
}
+
+ // Reload the website if returning from the Domains activity.
+ if (reloadWebsite) {
+ mainWebView.reload();
+ }
}
private void setDisplayWebpageImages() {
}
private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
- // Get handles for the icons.
- MenuItem privacyIconMenuItem = mainMenu.findItem(R.id.toggle_javascript);
- MenuItem firstPartyCookiesIconMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
- MenuItem domStorageIconMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
- MenuItem formDataIconMenuItem = mainMenu.findItem(R.id.toggle_save_form_data);
+ // Get handles for the menu items.
+ MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript);
+ MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies);
+ MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage);
+ MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh);
- // Update `privacyIcon`.
+ // Update the privacy icon.
if (javaScriptEnabled) { // JavaScript is enabled.
- privacyIconMenuItem.setIcon(R.drawable.javascript_enabled);
+ privacyMenuItem.setIcon(R.drawable.javascript_enabled);
} else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
- privacyIconMenuItem.setIcon(R.drawable.warning);
+ privacyMenuItem.setIcon(R.drawable.warning);
} else { // All the dangerous features are disabled.
- privacyIconMenuItem.setIcon(R.drawable.privacy_mode);
+ privacyMenuItem.setIcon(R.drawable.privacy_mode);
}
- // Update `firstPartyCookiesIcon`.
+ // Update the first-party cookies icon.
if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
- firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_enabled);
+ firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
} else { // First-party cookies are disabled.
if (darkTheme) {
- firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled_dark);
+ firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
} else {
- firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled_light);
+ firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
}
}
- // Update `domStorageIcon`.
+ // Update the DOM storage icon.
if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
- domStorageIconMenuItem.setIcon(R.drawable.dom_storage_enabled);
+ domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
} else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
if (darkTheme) {
- domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
+ domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
} else {
- domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
+ domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
}
} else { // JavaScript is disabled, so DOM storage is ghosted.
if (darkTheme) {
- domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
+ domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
} else {
- domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
+ domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
}
}
- // Update `formDataIcon`.
- if (saveFormDataEnabled) { // Form data is enabled.
- formDataIconMenuItem.setIcon(R.drawable.form_data_enabled);
- } else { // Form data is disabled.
- if (darkTheme) {
- formDataIconMenuItem.setIcon(R.drawable.form_data_disabled_dark);
- } else {
- formDataIconMenuItem.setIcon(R.drawable.form_data_disabled_light);
- }
+ // Update the refresh icon.
+ if (darkTheme) {
+ refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
+ } else {
+ refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
}
// `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
}
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`.
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.
bookmarksTitleTextView.setText(currentBookmarksFolder);
}
}
-}
+}
\ No newline at end of file