X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=7c13950a9334030c6d4d86a608046e4508975e96;hp=61b94c4ba7a4771b07ee932332a4bc713e958257;hb=de29e5f5b80d44fdc50bbb21379db860a7c7dfa2;hpb=c11722b31b7218695ba9a3c449ff586ae619731b diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 61b94c4b..7c13950a 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -23,6 +23,7 @@ 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; @@ -196,10 +197,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public static String easyPrivacyVersion; 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()`. + // 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 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; @@ -318,7 +328,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `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; // `displayWebpageImagesBoolean` is used in `applyAppSettings()` and `applyDomainSettings()`. @@ -330,11 +340,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `searchURL` is used in `loadURLFromTextBox()` and `applyAppSettings()`. private String searchURL; - // The block list variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, 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 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; @@ -372,12 +404,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`. private boolean ignorePinnedSslCertificate; + // `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; @@ -396,8 +434,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `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; @@ -425,13 +463,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `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()`. @@ -440,7 +479,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `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()`. @@ -525,12 +564,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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(); } @@ -563,13 +605,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; @@ -690,7 +732,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the `BannerAd` in the free flavor. if (BuildConfig.FLAVOR.contentEquals("free")) { // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. - AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id)); + AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id)); } // Remove the translucent navigation bar flag if it is set. @@ -860,7 +902,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; }); - // The `DrawerListener` is used 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) { @@ -876,33 +918,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onDrawerStateChanged(int newState) { - if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // The drawer is opening or closing. - // Initialize a the blocked requests counter. - int blockedRequests = 0; - - // Count the number of blocked requests. - for (int i = 0; i < resourceRequests.size(); i++) { - // Add the blocked requests. - if (Integer.valueOf(resourceRequests.get(i)[REQUEST_DISPOSITION]) == REQUEST_BLOCKED) { - blockedRequests++; - } - - // Add the third-party requests if they are blocked. - if (blockAllThirdPartyRequests && (Integer.valueOf(resourceRequests.get(i)[REQUEST_DISPOSITION]) == REQUEST_THIRD_PARTY)) { - blockedRequests++; - } - } - + 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(getResources().getString(R.string.requests) + " - " + blockedRequests); + 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(); } } @@ -927,10 +953,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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) { @@ -938,7 +964,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } }; - // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds. + // Displaying of `mainWebView` after 500 milliseconds. displayWebViewHandler.postDelayed(displayWebViewRunnable, 500); }); } @@ -1025,7 +1051,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onHideCustomView() { // Unset the full screen video flag. - displayingFullScreenVideo = true; + displayingFullScreenVideo = false; // Hide `fullScreenVideoFrameLayout`. fullScreenVideoFrameLayout.removeAllViews(); @@ -1075,7 +1101,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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.ad_id)); + 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`. @@ -1094,7 +1120,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the ad if this is the free flavor. if (BuildConfig.FLAVOR.contentEquals("free")) { // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. - AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id)); + AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id)); } } @@ -1132,7 +1158,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show a dialog if the user has previously denied the permission. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_FILE. + // 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. @@ -1141,7 +1167,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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. + } else { // The storage permission has already been granted. // Get a handle for the download file alert dialog. AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength); @@ -1171,7 +1197,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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. @@ -1203,6 +1229,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook saveFormDataEnabled = false; // Form data can be removed once the minimum API >= 26. nightMode = false; + // Store the default user agent. + webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString(); + // Initialize the WebView title. webViewTitle = getString(R.string.no_title); @@ -1235,12 +1264,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook final ArrayList> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt"); final ArrayList> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt"); final ArrayList> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt"); + final ArrayList> 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]; 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. @@ -1319,7 +1353,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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 `whiteListResultStringArray`. + // Reset the whitelist results tracker. whiteListResultStringArray = null; // Initialize the third party request tracker. @@ -1362,6 +1396,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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}); @@ -1369,9 +1414,51 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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(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; } @@ -1380,6 +1467,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check EasyPrivacy if it is enabled. if (easyPrivacyEnabled) { 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; } @@ -1388,11 +1489,39 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check Fanboy’s Annoyance List if it is enabled. 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 (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; } @@ -1401,7 +1530,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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 defult request. + } else { // The request didn't match any blocklist entry. Log it as a default request. resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url}); } @@ -1426,6 +1555,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); @@ -1456,17 +1594,52 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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 (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; @@ -1746,6 +1919,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); } @@ -1767,6 +1943,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @Override public void onPause() { + // Run the default commands. super.onPause(); // Pause `mainWebView`. @@ -1782,6 +1959,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } + @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 public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. @@ -1799,7 +1989,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage); 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. - MenuItem refreshMenuItem = menu.findItem(R.id.refresh); + 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 @@ -1812,11 +2009,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Only show Ad Consent if this is the free flavor. adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free")); - // Get the shared preference values. `this` references the current context. + // Get the shared preference values. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + // 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 (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) { + if (displayAdditionalAppBarIcons) { toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); @@ -1826,6 +2026,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; } @@ -1841,14 +2056,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); // Form data can be removed once the minimum API >= 26. - MenuItem easyListMenuItem = menu.findItem(R.id.easylist); - MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy); - MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list); - MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list); - MenuItem blockAllThirdParyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests); 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 nightModeMenuItem = menu.findItem(R.id.night_mode); // Set the text for the domain menu item. if (domainSettingsApplied) { @@ -1866,9 +2077,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook easyPrivacyMenuItem.setChecked(easyPrivacyEnabled); fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled); fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled); - blockAllThirdParyRequestsMenuItem.setChecked(blockAllThirdPartyRequests); + 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); @@ -1900,6 +2113,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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. @@ -1908,6 +2124,47 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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; @@ -2195,19 +2452,27 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); } } }) @@ -2240,61 +2505,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook .show(); return true; - case R.id.font_size_twenty_five_percent: - mainWebView.getSettings().setTextZoom(25); - return true; - - case R.id.font_size_fifty_percent: - mainWebView.getSettings().setTextZoom(50); - return true; - - case R.id.font_size_seventy_five_percent: - mainWebView.getSettings().setTextZoom(75); - return true; - - case R.id.font_size_one_hundred_percent: - mainWebView.getSettings().setTextZoom(100); - return true; - - case R.id.font_size_one_hundred_twenty_five_percent: - mainWebView.getSettings().setTextZoom(125); - return true; - - case R.id.font_size_one_hundred_fifty_percent: - mainWebView.getSettings().setTextZoom(150); - return true; - - case R.id.font_size_one_hundred_seventy_five_percent: - mainWebView.getSettings().setTextZoom(175); - return true; - - case R.id.font_size_two_hundred_percent: - 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); - mainWebView.reload(); - } else { // Images are not currently loaded automatically. - mainWebView.getSettings().setLoadsImagesAutomatically(true); - } - - // Set `onTheFlyDisplayImagesSet`. - onTheFlyDisplayImagesSet = true; - return true; - - case R.id.view_source: - // Launch the View Source activity. - Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); - startActivity(viewSourceIntent); - return true; - case R.id.easylist: // Toggle the EasyList status. easyListEnabled = !easyListEnabled; @@ -2343,6 +2553,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; @@ -2354,6 +2575,194 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; + + case R.id.font_size_fifty_percent: + mainWebView.getSettings().setTextZoom(50); + return true; + + case R.id.font_size_seventy_five_percent: + mainWebView.getSettings().setTextZoom(75); + return true; + + case R.id.font_size_one_hundred_percent: + mainWebView.getSettings().setTextZoom(100); + return true; + + case R.id.font_size_one_hundred_twenty_five_percent: + mainWebView.getSettings().setTextZoom(125); + return true; + + case R.id.font_size_one_hundred_fifty_percent: + mainWebView.getSettings().setTextZoom(150); + return true; + + case R.id.font_size_one_hundred_seventy_five_percent: + mainWebView.getSettings().setTextZoom(175); + return true; + + case R.id.font_size_two_hundred_percent: + 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); + mainWebView.reload(); + } else { // Images are not currently loaded automatically. + mainWebView.getSettings().setLoadsImagesAutomatically(true); + } + + // Set `onTheFlyDisplayImagesSet`. + 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(); @@ -2409,7 +2818,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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: @@ -2509,6 +2924,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -2522,6 +2943,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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); @@ -2660,7 +3085,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Reload the ad for the free flavor if we not in full screen mode. if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) { // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations. - AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_id)); + 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: @@ -2721,7 +3146,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show a dialog if the user has previously denied the permission. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_FILE. + // 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. @@ -2806,7 +3231,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show a dialog if the user has previously denied the permission. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. + // 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. @@ -2865,7 +3290,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show a dialog if the user has previously denied the permission. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Get a handle for the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE. + // 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. @@ -3018,7 +3443,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + 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. @@ -3373,9 +3798,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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`. @@ -3426,8 +3851,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Apply the domain settings. applyDomainSettings(url, true, false); - // Set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website. - urlIsLoading = true; + // 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); @@ -3465,11 +3890,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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", ""); incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false); boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false); @@ -3511,6 +3936,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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); } @@ -3595,7 +4023,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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.ad_id)); + 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`. @@ -3653,8 +4081,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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`. @@ -3709,7 +4136,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store the general preference information. String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); String defaultUserAgentName = sharedPreferences.getString("user_agent", "Privacy Browser"); - String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); nightMode = sharedPreferences.getBoolean("night_mode", false); @@ -3730,6 +4157,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook easyPrivacyEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1); fanboysAnnoyanceListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1); fanboysSocialBlockingListEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1); + 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)); @@ -3755,7 +4183,10 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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; } @@ -3886,6 +4317,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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`. @@ -3958,7 +4390,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook 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.