+ // 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();
+
+ // Consume the event.
+ return true;
+
+ case R.id.easyprivacy:
+ // Toggle the EasyPrivacy status.
+ currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.fanboys_annoyance_list:
+ // Toggle Fanboy's Annoyance List status.
+ currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+
+ // Update the staus of Fanboy's Social Blocking List.
+ MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
+ fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.fanboys_social_blocking_list:
+ // Toggle Fanboy's Social Blocking List status.
+ currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.ultralist:
+ // Toggle the UltraList status.
+ currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.ultraprivacy:
+ // Toggle the UltraPrivacy status.
+ currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.block_all_third_party_requests:
+ //Toggle the third-party requests blocker status.
+ currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+
+ // Update the menu checkbox.
+ menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.proxy_none:
+ // Update the proxy mode.
+ proxyMode = ProxyHelper.NONE;
+
+ // Apply the proxy mode.
+ applyProxy(true);
+
+ // Consume the event.
+ return true;
+
+ case R.id.proxy_tor:
+ // Update the proxy mode.
+ proxyMode = ProxyHelper.TOR;
+
+ // Apply the proxy mode.
+ applyProxy(true);
+
+ // Consume the event.
+ return true;
+
+ case R.id.proxy_i2p:
+ // Update the proxy mode.
+ proxyMode = ProxyHelper.I2P;
+
+ // Apply the proxy mode.
+ applyProxy(true);
+
+ // Consume the event.
+ return true;
+
+ case R.id.proxy_custom:
+ // Update the proxy mode.
+ proxyMode = ProxyHelper.CUSTOM;
+
+ // Apply the proxy mode.
+ applyProxy(true);
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_privacy_browser:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_webview_default:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString("");
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_firefox_on_android:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_chrome_on_android:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_safari_on_ios:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_firefox_on_linux:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_chromium_on_linux:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_firefox_on_windows:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_chrome_on_windows:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_edge_on_windows:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_internet_explorer_on_windows:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_safari_on_macos:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.user_agent_custom:
+ // Update the user agent.
+ currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
+
+ // Reload the current WebView.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.font_size:
+ // Instantiate the font size dialog.
+ DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
+
+ // Show the font size dialog.
+ fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
+
+ // Consume the event.
+ return true;
+
+ case R.id.swipe_to_refresh:
+ // Toggle the stored status of swipe to refresh.
+ currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
+
+ // Get a handle for the swipe refresh layout.
+ SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
+ // Update the swipe refresh layout.
+ if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
+ // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
+ swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
+ } else { // Swipe to refresh is disabled.
+ // Disable the swipe refresh layout.
+ swipeRefreshLayout.setEnabled(false);
+ }
+
+ // Consume the event.
+ return true;
+
+ case R.id.wide_viewport:
+ // Toggle the viewport.
+ currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
+
+ // Consume the event.
+ return true;
+
+ case R.id.display_images:
+ if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
+ // Disable loading of images.
+ currentWebView.getSettings().setLoadsImagesAutomatically(false);
+
+ // Reload the website to remove existing images.
+ currentWebView.reload();
+ } else { // Images are not currently loaded automatically.
+ // Enable loading of images. Missing images will be loaded without the need for a reload.
+ currentWebView.getSettings().setLoadsImagesAutomatically(true);
+ }
+
+ // Consume the event.
+ return true;
+
+ case R.id.night_mode:
+ // Toggle night mode.
+ currentWebView.setNightMode(!currentWebView.getNightMode());
+
+ // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
+ if (currentWebView.getNightMode()) { // Night mode is enabled, which requires JavaScript.
+ // Enable JavaScript.
+ currentWebView.getSettings().setJavaScriptEnabled(true);
+ } else if (currentWebView.getDomainSettingsApplied()) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
+ // Apply the JavaScript preference that was stored the last time domain settings were loaded.
+ currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled());
+ } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
+ // Apply the JavaScript preference.
+ currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
+ }
+
+ // Update the privacy icons.
+ updatePrivacyIcons(false);
+
+ // Reload the website.
+ currentWebView.reload();
+
+ // Consume the event.
+ return true;
+
+ case R.id.find_on_page:
+ // Get a handle for the views.
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+ EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+
+ // Set the minimum height of the find on page linear layout to match the toolbar.
+ findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
+
+ // Hide the toolbar.
+ toolbar.setVisibility(View.GONE);
+
+ // Show the find on page linear layout.
+ findOnPageLinearLayout.setVisibility(View.VISIBLE);
+
+ // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android.
+ // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
+ findOnPageEditText.postDelayed(() -> {
+ // Set the focus on `findOnPageEditText`.
+ findOnPageEditText.requestFocus();
+
+ // Get a handle for the input method manager.
+ InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ // Remove the lint warning below that the input method manager might be null.
+ assert inputMethodManager != null;
+
+ // Display the keyboard. `0` sets no input flags.
+ inputMethodManager.showSoftInput(findOnPageEditText, 0);
+ }, 200);
+
+ // Consume the event.
+ return true;
+
+ case R.id.print:
+ // Get a print manager instance.
+ PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+
+ // Remove the lint error below that print manager might be null.
+ assert printManager != null;
+
+ // Create a print document adapter from the current WebView.
+ PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
+
+ // Print the document.
+ printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+
+ // Consume the event.
+ return true;
+
+ case R.id.save_as_archive:
+ // Instantiate the save webpage archive dialog.
+ DialogFragment saveWebpageArchiveDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_ARCHIVE);
+
+ // Show the save webpage archive dialog.
+ saveWebpageArchiveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_webpage));
+
+ // Consume the event.
+ return true;
+
+ case R.id.save_as_image:
+ // Instantiate the save webpage image dialog.
+ DialogFragment saveWebpageImageDialogFragment = SaveWebpageDialog.saveWebpage(StoragePermissionDialog.SAVE_IMAGE);
+
+ // Show the save webpage image dialog.
+ saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_webpage));