+ // Resume the nested scroll WebView JavaScript timers.
+ nestedScrollWebView.resumeTimers();
+
+ // Resume the nested scroll WebView.
+ nestedScrollWebView.onResume();
+ }
+ }
+
+ // Reapply the proxy settings if the system is using a proxy. This redisplays the appropriate alert dialog.
+ if (!proxyMode.equals(ProxyHelper.NONE)) {
+ applyProxy(false);
+ }
+
+ // Reapply any system UI flags and the ad in the free flavor.
+ if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode.
+ // Get a handle for the root frame layouts.
+ FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+
+ // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * 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.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ } else if (BuildConfig.FLAVOR.contentEquals("free")) { // The system in not in full screen mode.
+ // Resume the ad.
+ AdHelper.resumeAd(findViewById(R.id.adview));
+ }
+ }
+
+ @Override
+ public void onPause() {
+ // Run the default commands.
+ super.onPause();
+
+ for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
+ // Get the WebView tab fragment.
+ WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
+
+ // Get the fragment view.
+ View fragmentView = webViewTabFragment.getView();
+
+ // Only pause the WebViews if they exist (they won't when the app is first created).
+ if (fragmentView != null) {
+ // Get the nested scroll WebView from the tab fragment.
+ NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
+
+ // Pause the nested scroll WebView.
+ nestedScrollWebView.onPause();
+
+ // Pause the nested scroll WebView JavaScript timers.
+ nestedScrollWebView.pauseTimers();
+ }
+ }
+
+ // Pause the ad or it will continue to consume resources in the background on the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ // Pause the ad.
+ AdHelper.pauseAd(findViewById(R.id.adview));
+ }
+ }
+
+ @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.
+ getMenuInflater().inflate(R.menu.webview_options_menu, menu);
+
+ // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
+ optionsMenu = menu;
+
+ // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
+ updatePrivacyIcons(false);
+
+ // Get handles for the menu items.
+ 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); // 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);
+ MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
+
+ // Only display third-party cookies if API >= 21
+ toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
+
+ // 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);
+
+ // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
+ clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
+
+ // Only show Ad Consent if this is the free flavor.
+ adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
+
+ // Get the shared preferences.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // Get the dark theme and app bar preferences..
+ boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
+ boolean darkTheme = sharedPreferences.getBoolean("dark_theme", 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);
+ 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);
+ refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ }
+
+ // Replace Refresh with Stop if a URL is already loading.
+ if (currentWebView != null && currentWebView.getProgress() != 100) {
+ // 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);
+ }
+ }
+ }
+
+ // Done.
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ // Get handles for the menu items.
+ MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
+ MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
+ MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
+ MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
+ MenuItem saveFormDataMenuItem = 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); // Form data can be removed once the minimum API >= 26.
+ MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
+ 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 ultraListMenuItem = menu.findItem(R.id.ultralist);
+ MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
+ MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
+ MenuItem proxyMenuItem = menu.findItem(R.id.proxy);
+ MenuItem userAgentMenuItem = menu.findItem(R.id.user_agent);
+ MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
+ MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
+ MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
+ MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
+ MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
+
+ // Get a handle for the cookie manager.
+ CookieManager cookieManager = CookieManager.getInstance();
+
+ // Initialize the current user agent string and the font size.
+ String currentUserAgent = getString(R.string.user_agent_privacy_browser);
+ int fontSize = 100;
+
+ // Set items that require the current web view to be populated. It will be null when the program is first opened, as `onPrepareOptionsMenu()` is called before the first WebView is initialized.
+ if (currentWebView != null) {
+ // Set the add or edit domain text.
+ if (currentWebView.getDomainSettingsApplied()) {
+ addOrEditDomain.setTitle(R.string.edit_domain_settings);
+ } else {
+ addOrEditDomain.setTitle(R.string.add_domain_settings);
+ }
+
+ // Get the current user agent from the WebView.
+ currentUserAgent = currentWebView.getSettings().getUserAgentString();
+
+ // Get the current font size from the
+ fontSize = currentWebView.getSettings().getTextZoom();
+
+ // Set the status of the menu item checkboxes.
+ domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
+ saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26.
+ easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+ easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+ fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+ fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+ ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+ ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+ blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+ swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
+ wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
+ displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
+ nightModeMenuItem.setChecked(currentWebView.getNightMode());
+
+ // Initialize the display names for the blocklists with the number of blocked requests.
+ blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
+ easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
+ easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
+ fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
+ fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
+ ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
+ ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
+ blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
+
+ // Only modify third-party cookies if the API >= 21.
+ if (Build.VERSION.SDK_INT >= 21) {
+ // Set the status of the third-party cookies checkbox.
+ thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
+
+ // Enable third-party cookies if first-party cookies are enabled.
+ thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
+ }
+
+ // Enable DOM Storage if JavaScript is enabled.
+ domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
+ }
+
+ // Set the checked status of the first party cookies menu item.
+ firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
+
+ // Enable Clear Cookies if there are any.
+ clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
+
+ // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+ String privateDataDirectoryString = getApplicationInfo().dataDir;
+
+ // 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()) {
+ // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
+ localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
+ }
+
+ // 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()) {
+ // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
+ indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
+ }
+
+ // Enable Clear DOM Storage if there is any.
+ clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
+
+ // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
+ if (Build.VERSION.SDK_INT < 26) {
+ // Get the WebView database.
+ WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
+
+ // Enable the clear form data menu item if there is anything to clear.
+ clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
+ }
+
+ // 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 menu item if Fanboy's Annoyance List is checked.
+ fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
+
+ // Set the proxy title and check the applied proxy.
+ switch (proxyMode) {
+ case ProxyHelper.NONE:
+ // Set the proxy title.
+ proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
+
+ // Check the proxy None radio button.
+ menu.findItem(R.id.proxy_none).setChecked(true);
+ break;
+
+ case ProxyHelper.TOR:
+ // Set the proxy title.
+ proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
+
+ // Check the proxy Tor radio button.
+ menu.findItem(R.id.proxy_tor).setChecked(true);
+ break;
+
+ case ProxyHelper.I2P:
+ // Set the proxy title.
+ proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
+
+ // Check the proxy I2P radio button.
+ menu.findItem(R.id.proxy_i2p).setChecked(true);
+ break;
+
+ case ProxyHelper.CUSTOM:
+ // Set the proxy title.
+ proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
+
+ // Check the proxy Custom radio button.
+ menu.findItem(R.id.proxy_custom).setChecked(true);
+ break;
+ }
+
+ // 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
+
+ // Select the Privacy Browser radio box.
+ menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
+ } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
+
+ // Select the WebView Default radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
+
+ // Select the Firefox on Android radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
+
+ // Select the Chrome on Android radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
+
+ // Select the Safari on iOS radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
+
+ // Select the Firefox on Linux radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
+
+ // Select the Chromium on Linux radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
+
+ // Select the Firefox on Windows radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
+
+ // Select the Chrome on Windows radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
+
+ // Select the Edge on Windows radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
+
+ // Select the Internet on Windows radio box.
+ 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.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
+
+ // Select the Safari on macOS radio box.
+ menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
+ } else { // Custom user agent.
+ // Update the user agent menu item title.
+ userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
+
+ // Select the Custom radio box.
+ menu.findItem(R.id.user_agent_custom).setChecked(true);
+ }
+
+ // Set the font size title.
+ fontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
+
+ // Run all the other default commands.
+ super.onPrepareOptionsMenu(menu);
+
+ // Display the menu.
+ return true;
+ }
+
+ @Override
+ // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
+ @SuppressLint("SetJavaScriptEnabled")
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ // Get the selected menu item ID.
+ int menuItemId = menuItem.getItemId();
+
+ // Get a handle for the shared preferences.
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // Get a handle for the cookie manager.
+ CookieManager cookieManager = CookieManager.getInstance();
+
+ // Run the commands that correlate to the selected menu item.
+ switch (menuItemId) {
+ case R.id.toggle_javascript:
+ // Toggle the JavaScript status.
+ currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
+
+ // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
+ updatePrivacyIcons(true);
+
+ // Display a `Snackbar`.
+ if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled.
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
+ } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled.
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
+ } else { // Privacy mode.
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
+ }
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.add_or_edit_domain:
+ if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings.
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
+
+ // Create an intent to launch the domains activity.
+ Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+ // Add the extra information to the intent.
+ domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
+ domainsIntent.putExtra("close_on_back", true);
+ domainsIntent.putExtra("current_url", currentWebView.getUrl());
+
+ // Get the current certificate.
+ SslCertificate sslCertificate = currentWebView.getCertificate();
+
+ // Check to see if the SSL certificate is populated.
+ if (sslCertificate != null) {
+ // Extract the certificate to strings.
+ String issuedToCName = sslCertificate.getIssuedTo().getCName();
+ String issuedToOName = sslCertificate.getIssuedTo().getOName();
+ String issuedToUName = sslCertificate.getIssuedTo().getUName();
+ String issuedByCName = sslCertificate.getIssuedBy().getCName();
+ String issuedByOName = sslCertificate.getIssuedBy().getOName();
+ String issuedByUName = sslCertificate.getIssuedBy().getUName();
+ long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
+ long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+
+ // Add the certificate to the intent.
+ domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
+ domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
+ domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
+ domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
+ domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
+ domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
+ domainsIntent.putExtra("ssl_start_date", startDateLong);
+ domainsIntent.putExtra("ssl_end_date", endDateLong);
+ }
+
+ // Check to see if the current IP addresses have been received.
+ if (currentWebView.hasCurrentIpAddresses()) {
+ // Add the current IP addresses to the intent.
+ domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+ }
+
+ // Make it so.
+ startActivity(domainsIntent);
+ } else { // Add a new domain.
+ // Apply the new domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
+
+ // Get the current domain
+ Uri currentUri = Uri.parse(currentWebView.getUrl());
+ 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);
+
+ // Add the extra information to the intent.
+ domainsIntent.putExtra("load_domain", newDomainDatabaseId);
+ domainsIntent.putExtra("close_on_back", true);
+ domainsIntent.putExtra("current_url", currentWebView.getUrl());
+
+ // Get the current certificate.
+ SslCertificate sslCertificate = currentWebView.getCertificate();
+
+ // Check to see if the SSL certificate is populated.
+ if (sslCertificate != null) {
+ // Extract the certificate to strings.
+ String issuedToCName = sslCertificate.getIssuedTo().getCName();
+ String issuedToOName = sslCertificate.getIssuedTo().getOName();
+ String issuedToUName = sslCertificate.getIssuedTo().getUName();
+ String issuedByCName = sslCertificate.getIssuedBy().getCName();
+ String issuedByOName = sslCertificate.getIssuedBy().getOName();
+ String issuedByUName = sslCertificate.getIssuedBy().getUName();
+ long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
+ long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
+
+ // Add the certificate to the intent.
+ domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
+ domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
+ domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
+ domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
+ domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
+ domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
+ domainsIntent.putExtra("ssl_start_date", startDateLong);
+ domainsIntent.putExtra("ssl_end_date", endDateLong);
+ }
+
+ // Check to see if the current IP addresses have been received.
+ if (currentWebView.hasCurrentIpAddresses()) {
+ // Add the current IP addresses to the intent.
+ domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
+ }
+
+ // Make it so.
+ startActivity(domainsIntent);
+ }
+
+ // Consume the event.
+ return true;
+
+ case R.id.toggle_first_party_cookies:
+ // Switch the first-party cookie status.
+ cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
+
+ // Store the first-party cookie status.
+ currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
+
+ // Update the menu checkbox.
+ menuItem.setChecked(cookieManager.acceptCookie());
+
+ // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
+ updatePrivacyIcons(true);
+
+ // Display a snackbar.
+ if (cookieManager.acceptCookie()) { // First-party cookies are enabled.
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+ } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled.
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+ } else { // Privacy mode.
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
+ }
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.toggle_third_party_cookies:
+ if (Build.VERSION.SDK_INT >= 21) {
+ // Switch the status of thirdPartyCookiesEnabled.
+ cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
+
+ // Display a snackbar.
+ if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
+ } else {
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
+ }
+
+ // Reload the current WebView.
+ currentWebView.reload();
+ } // Else do nothing because SDK < 21.
+
+ // Consume the event.
+ return true;
+
+ case R.id.toggle_dom_storage:
+ // Toggle the status of domStorageEnabled.
+ currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
+
+ // Update the privacy icon. `true` refreshes the app bar icons.
+ updatePrivacyIcons(true);
+
+ // Display a snackbar.
+ if (currentWebView.getSettings().getDomStorageEnabled()) {
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
+ } else {
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
+ }
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ // Form data can be removed once the minimum API >= 26.
+ case R.id.toggle_save_form_data:
+ // Switch the status of saveFormDataEnabled.
+ currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
+
+ // Display a snackbar.
+ if (currentWebView.getSettings().getSaveFormData()) {
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
+ } else {
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
+ }
+
+ // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
+ updatePrivacyIcons(true);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.clear_cookies:
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
+ .setAction(R.string.undo, v -> {
+ // Do nothing because everything will be handled by `onDismissed()` below.
+ })
+ .addCallback(new Snackbar.Callback() {
+ @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+ @Override
+ public void onDismissed(Snackbar snackbar, int event) {
+ if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
+ // Delete the cookies, which command varies by SDK.
+ if (Build.VERSION.SDK_INT < 21) {
+ cookieManager.removeAllCookie();
+ } else {
+ cookieManager.removeAllCookies(null);
+ }
+ }
+ }
+ })
+ .show();
+
+ // Consume the event.
+ return true;
+
+ case R.id.clear_dom_storage:
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
+ .setAction(R.string.undo, v -> {
+ // Do nothing because everything will be handled by `onDismissed()` below.
+ })
+ .addCallback(new Snackbar.Callback() {
+ @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+ @Override
+ public void onDismissed(Snackbar snackbar, int event) {
+ if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
+ // Delete the DOM Storage.
+ WebStorage webStorage = WebStorage.getInstance();
+ webStorage.deleteAllData();
+
+ // 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 {
+ // Get a handle for the runtime.
+ Runtime runtime = Runtime.getRuntime();
+
+ // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
+ // which links to `/data/data/com.stoutner.privacybrowser.standard`.
+ String privateDataDirectoryString = getApplicationInfo().dataDir;
+
+ // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+ Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+ // Multiple commands must be used because `Runtime.exec()` does not like `*`.
+ Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+ Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+ Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+ Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+
+ // Wait for the processes to finish.
+ deleteLocalStorageProcess.waitFor();
+ deleteIndexProcess.waitFor();
+ deleteQuotaManagerProcess.waitFor();
+ deleteQuotaManagerJournalProcess.waitFor();
+ deleteDatabasesProcess.waitFor();
+ } catch (Exception exception) {
+ // Do nothing if an error is thrown.
+ }
+ };
+
+ // Manually delete the DOM storage files after 200 milliseconds.
+ deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
+ }
+ }
+ })
+ .show();
+
+ // Consume the event.
+ return true;
+
+ // Form data can be remove once the minimum API >= 26.
+ case R.id.clear_form_data:
+ Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
+ .setAction(R.string.undo, v -> {
+ // Do nothing because everything will be handled by `onDismissed()` below.
+ })
+ .addCallback(new Snackbar.Callback() {
+ @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
+ @Override
+ public void onDismissed(Snackbar snackbar, int event) {
+ if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
+ // Delete the form data.
+ WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
+ mainWebViewDatabase.clearFormData();
+ }
+ }
+ })
+ .show();
+
+ // Consume the event.
+ return true;
+
+ case R.id.easylist:
+ // Toggle the EasyList status.
+ currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
+
+ // Reload the current WebView.
+ currentWebView.reload();