2 * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser;
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.app.DialogFragment;
25 import android.app.DownloadManager;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.SharedPreferences;
29 import android.content.res.Configuration;
30 import android.graphics.Bitmap;
31 import android.graphics.drawable.BitmapDrawable;
32 import android.graphics.drawable.Drawable;
33 import android.net.Uri;
34 import android.net.http.SslError;
35 import android.os.Build;
36 import android.os.Bundle;
37 import android.preference.PreferenceManager;
38 import android.support.design.widget.NavigationView;
39 import android.support.design.widget.Snackbar;
40 import android.support.v4.app.ActivityCompat;
41 import android.support.v4.content.ContextCompat;
42 import android.support.v4.view.GravityCompat;
43 import android.support.v4.widget.DrawerLayout;
44 import android.support.v4.widget.SwipeRefreshLayout;
45 import android.support.v7.app.ActionBar;
46 import android.support.v7.app.ActionBarDrawerToggle;
47 import android.support.v7.app.AppCompatActivity;
48 import android.support.v7.widget.Toolbar;
49 import android.util.Patterns;
50 import android.view.KeyEvent;
51 import android.view.Menu;
52 import android.view.MenuItem;
53 import android.view.View;
54 import android.view.inputmethod.InputMethodManager;
55 import android.webkit.CookieManager;
56 import android.webkit.DownloadListener;
57 import android.webkit.SslErrorHandler;
58 import android.webkit.WebChromeClient;
59 import android.webkit.WebStorage;
60 import android.webkit.WebView;
61 import android.webkit.WebViewClient;
62 import android.webkit.WebViewDatabase;
63 import android.widget.EditText;
64 import android.widget.FrameLayout;
65 import android.widget.ImageView;
66 import android.widget.ProgressBar;
68 import java.io.UnsupportedEncodingException;
69 import java.net.MalformedURLException;
71 import java.net.URLEncoder;
72 import java.util.HashMap;
75 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
76 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener,
77 SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener {
78 // `privacyBrowserContext` is public static so it can be accessed from `SettingsFragment`.
79 // It is also used in `onCreate()` and `onConfigurationChanged()`.
80 public static Context privacyBrowserContext;
82 // `appBar` is public static so it can be accessed from `OrbotProxyHelper`.
83 // It is also used in `onCreate()`.
84 public static ActionBar appBar;
86 // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
87 // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
88 public static Bitmap favoriteIcon;
90 // `privacyBrowserActivity` is public static so it can be accessed from `SettingsFragment`.
91 // It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `onOptionsItemSelected()`,
92 public static Activity privacyBrowserActivity;
94 // `mainWebView` is public static so it can be accessed from `SettingsFragment`.
95 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, and `loadUrlFromTextBox()`.
96 public static WebView mainWebView;
98 // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`.
99 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
100 public static String formattedUrlString;
102 // `mainMenu` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreateOptionsMenu()` and `onOptionsItemSelected()`.
103 public static Menu mainMenu;
105 // `cookieManager` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
106 public static CookieManager cookieManager;
108 // `javaScriptEnabled` is public static so it can be accessed from `SettingsFragment`.
109 // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
110 public static boolean javaScriptEnabled;
112 // `firstPartyCookiesEnabled` is public static so it can be accessed from `SettingsFragment`.
113 // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, and `onOptionsItemSelected()`.
114 public static boolean firstPartyCookiesEnabled;
116 // `thirdPartyCookiesEnables` is public static so it can be accessed from `SettingsFragment`.
117 // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, and `onOptionsItemSelected()`.
118 public static boolean thirdPartyCookiesEnabled;
120 // `domStorageEnabled` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `onOptionsItemSelected()`.
121 public static boolean domStorageEnabled;
123 // `saveFormDataEnabled` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `onOptionsItemSelected()`.
124 public static boolean saveFormDataEnabled;
126 // `javaScriptDisabledSearchURL` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()` and `loadURLFromTextBox()`.
127 public static String javaScriptDisabledSearchURL;
129 // `javaScriptEnabledSearchURL` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()` and `loadURLFromTextBox()`.
130 public static String javaScriptEnabledSearchURL;
132 // `homepage` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()` and `onOptionsItemSelected()`.
133 public static String homepage;
135 // `swipeToRefresh` is public static so it can be accessed from SettingsFragment. It is also used in onCreate().
136 public static SwipeRefreshLayout swipeToRefresh;
138 // `swipeToRefreshEnabled` is public static so it can be accessed from `SettingsFragment`. It is also used in `onCreate()`.
139 public static boolean swipeToRefreshEnabled;
141 // `customHeader` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
142 public static Map<String, String> customHeaders = new HashMap<String, String>();
146 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
147 private ActionBarDrawerToggle drawerToggle;
149 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
150 private DrawerLayout drawerLayout;
152 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
153 private EditText urlTextBox;
155 // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
158 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
159 private SslErrorHandler sslErrorHandler;
161 // `sharedPreferences` is used in `onCreate()` and `onCreateOptionsMenu()`.
162 SharedPreferences sharedPreferences;
165 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers.
166 @SuppressLint("SetJavaScriptEnabled")
167 protected void onCreate(Bundle savedInstanceState) {
168 super.onCreate(savedInstanceState);
169 setContentView(R.layout.main_coordinatorlayout);
171 // We need a handle for the activity, which is accessed from `SettingsFragment` and fed into `updatePrivacyIcons()`.
172 privacyBrowserActivity = this;
174 // Get a handle for the application context.
175 privacyBrowserContext = getApplicationContext();
177 // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
178 Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
179 setSupportActionBar(supportAppBar);
180 appBar = getSupportActionBar();
182 // This is needed to get rid of the Android Studio warning that appBar might be null.
183 assert appBar != null;
185 // Add the custom url_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
186 appBar.setCustomView(R.layout.url_bar);
187 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
189 // Set the "go" button on the keyboard to load the URL in urlTextBox.
190 urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
191 urlTextBox.setOnKeyListener(new View.OnKeyListener() {
192 public boolean onKey(View v, int keyCode, KeyEvent event) {
193 // If the event is a key-down event on the "enter" button, load the URL.
194 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
195 // Load the URL into the mainWebView and consume the event.
197 loadUrlFromTextBox();
198 } catch (UnsupportedEncodingException e) {
201 // If the enter key was pressed, consume the event.
204 // If any other key was pressed, do not consume the event.
210 final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
212 // Implement swipe to refresh
213 swipeToRefresh = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
214 assert swipeToRefresh != null; //This assert removes the incorrect warning on the following line that swipeToRefresh might be null.
215 swipeToRefresh.setColorSchemeResources(R.color.blue_700);
216 swipeToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
218 public void onRefresh() {
219 mainWebView.reload();
223 mainWebView = (WebView) findViewById(R.id.mainWebView);
225 // Create the navigation drawer.
226 drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
227 // The DrawerTitle identifies the drawer in accessibility mode.
228 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
230 // Listen for touches on the navigation menu.
231 final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
232 assert navigationView != null; // This assert removes the incorrect warning on the following line that navigationView might be null.
233 navigationView.setNavigationItemSelectedListener(this);
235 // drawerToggle creates the hamburger icon at the start of the AppBar.
236 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
238 mainWebView.setWebViewClient(new WebViewClient() {
239 // shouldOverrideUrlLoading makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
241 public boolean shouldOverrideUrlLoading(WebView view, String url) {
242 // Use an external email program if the link begins with "mailto:".
243 if (url.startsWith("mailto:")) {
244 // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
245 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
247 // Parse the url and set it as the data for the `Intent`.
248 emailIntent.setData(Uri.parse(url));
250 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
251 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
254 startActivity(emailIntent);
256 } else { // Load the URL in Privacy Browser.
257 mainWebView.loadUrl(url, customHeaders);
262 // Update the URL in urlTextBox when the page starts to load.
264 public void onPageStarted(WebView view, String url, Bitmap favicon) {
265 urlTextBox.setText(url);
268 // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load.
270 public void onPageFinished(WebView view, String url) {
271 formattedUrlString = url;
273 // Only update urlTextBox if the user is not typing in it.
274 if (!urlTextBox.hasFocus()) {
275 urlTextBox.setText(formattedUrlString);
279 // Handle SSL Certificate errors.
281 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
282 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
283 sslErrorHandler = handler;
285 // Display the SSL error `AlertDialog`.
286 DialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error);
287 sslCertificateErrorDialogFragment.show(getFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
291 mainWebView.setWebChromeClient(new WebChromeClient() {
292 // Update the progress bar when a page is loading.
294 public void onProgressChanged(WebView view, int progress) {
295 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
296 progressBar.setProgress(progress);
297 if (progress < 100) {
298 progressBar.setVisibility(View.VISIBLE);
300 progressBar.setVisibility(View.GONE);
302 //Stop the SwipeToRefresh indicator if it is running
303 swipeToRefresh.setRefreshing(false);
307 // Set the favorite icon when it changes.
309 public void onReceivedIcon(WebView view, Bitmap icon) {
310 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
313 // Place the favorite icon in the appBar.
314 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
315 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
318 // Enter full screen video
320 public void onShowCustomView(View view, CustomViewCallback callback) {
323 // Show the fullScreenVideoFrameLayout.
324 assert fullScreenVideoFrameLayout != null; //This assert removes the incorrect warning on the following line that fullScreenVideoFrameLayout might be null.
325 fullScreenVideoFrameLayout.addView(view);
326 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
328 // Hide the mainWebView.
329 mainWebView.setVisibility(View.GONE);
331 // Hide the ad if this is the free flavor.
332 BannerAd.hideAd(adView);
334 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
335 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
336 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
338 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
341 // Exit full screen video
342 public void onHideCustomView() {
345 // Show the mainWebView.
346 mainWebView.setVisibility(View.VISIBLE);
348 // Show the ad if this is the free flavor.
349 BannerAd.showAd(adView);
351 // Hide the fullScreenVideoFrameLayout.
352 assert fullScreenVideoFrameLayout != null; //This assert removes the incorrect warning on the following line that fullScreenVideoFrameLayout might be null.
353 fullScreenVideoFrameLayout.removeAllViews();
354 fullScreenVideoFrameLayout.setVisibility(View.GONE);
358 // Allow the downloading of files.
359 mainWebView.setDownloadListener(new DownloadListener() {
361 public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
362 // Show the `DownloadFile` `AlertDialog` and name this instance `@string/download`.
363 DialogFragment downloadFileDialogFragment = DownloadFile.fromUrl(url, contentDisposition, contentLength);
364 downloadFileDialogFragment.show(getFragmentManager(), getResources().getString(R.string.download));
368 // Allow pinch to zoom.
369 mainWebView.getSettings().setBuiltInZoomControls(true);
371 // Hide zoom controls.
372 mainWebView.getSettings().setDisplayZoomControls(false);
375 // Initialize the default preference values the first time the program is run.
376 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
378 // Get the shared preference values.
379 sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
381 // Set JavaScript initial status. The default value is false.
382 javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
383 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
385 // Initialize cookieManager.
386 cookieManager = CookieManager.getInstance();
388 // Set cookies initial status. The default value is false.
389 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
390 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
392 // Set third-party cookies initial status if API >= 21. The default value is false.
393 if (Build.VERSION.SDK_INT >= 21) {
394 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
395 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
398 // Set DOM storage initial status. The default value is false.
399 domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
400 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
402 // Set the saved form data initial status. The default is false.
403 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
404 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
406 // Set the user agent initial status.
407 String userAgentString = sharedPreferences.getString("user_agent", "Default user agent");
408 switch (userAgentString) {
409 case "Default user agent":
413 case "Custom user agent":
414 // Set the custom user agent on mainWebView, The default is "PrivacyBrowser/1.0".
415 mainWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
419 // Set the selected user agent on mainWebView. The default is "PrivacyBrowser/1.0".
420 mainWebView.getSettings().setUserAgentString(sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"));
424 // Set the initial string for JavaScript disabled search.
425 if (sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=").equals("Custom URL")) {
426 // Get the custom URL string. The default is "".
427 javaScriptDisabledSearchURL = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
429 // Use the string from javascript_disabled_search.
430 javaScriptDisabledSearchURL = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
433 // Set the initial string for JavaScript enabled search.
434 if (sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=").equals("Custom URL")) {
435 // Get the custom URL string. The default is "".
436 javaScriptEnabledSearchURL = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
438 // Use the string from javascript_enabled_search.
439 javaScriptEnabledSearchURL = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
443 // Set the homepage initial status. The default value is `https://www.duckduckgo.com`.
444 homepage = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
446 // Set the font size initial status. the default value is `100`.
447 String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
448 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
450 // Set the swipe to refresh initial status. The default is `true`.
451 swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", true);
452 swipeToRefresh.setEnabled(swipeToRefreshEnabled);
455 // 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).
456 customHeaders.put("X-Requested-With", "");
458 // Set Do Not Track. The default is true.
459 if (sharedPreferences.getBoolean("do_not_track", true)) {
460 customHeaders.put("DNT", "1");
463 // Set Orbot proxy status. The default is `false`.
464 if (sharedPreferences.getBoolean("proxy_through_orbot", false)) {
465 OrbotProxyHelper.setProxy(privacyBrowserContext, privacyBrowserActivity, "localhost", "8118");
468 // Get the intent information that started the app.
469 final Intent intent = getIntent();
471 if (intent.getData() != null) {
472 // Get the intent data and convert it to a string.
473 final Uri intentUriData = intent.getData();
474 formattedUrlString = intentUriData.toString();
477 // If formattedUrlString is null assign the homepage to it.
478 if (formattedUrlString == null) {
479 formattedUrlString = homepage;
482 // Load the initial website.
483 mainWebView.loadUrl(formattedUrlString, customHeaders);
485 // If the favorite icon is null, load the default.
486 if (favoriteIcon == null) {
487 // We have to use `ContextCompat` until API >= 21.
488 Drawable favoriteIconDrawable = ContextCompat.getDrawable(privacyBrowserContext, R.drawable.world);
489 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
490 favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
493 // Initialize AdView for the free flavor and request an ad. If this is not the free flavor BannerAd.requestAd() does nothing.
494 adView = findViewById(R.id.adView);
495 BannerAd.requestAd(adView);
500 protected void onNewIntent(Intent intent) {
501 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
504 if (intent.getData() != null) {
505 // Get the intent data and convert it to a string.
506 final Uri intentUriData = intent.getData();
507 formattedUrlString = intentUriData.toString();
510 // Close the navigation drawer if it is open.
511 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
512 drawerLayout.closeDrawer(GravityCompat.START);
516 mainWebView.loadUrl(formattedUrlString, customHeaders);
518 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
519 mainWebView.requestFocus();
523 public boolean onCreateOptionsMenu(Menu menu) {
524 // Inflate the menu; this adds items to the action bar if it is present.
525 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
527 // Set mainMenu so it can be used by onOptionsItemSelected.
530 // Set the initial status of the privacy icon.
531 updatePrivacyIcons(privacyBrowserActivity);
533 // Get handles for the menu items.
534 MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
535 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
536 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
537 MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
539 // Set the initial status of the menu item checkboxes.
540 toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
541 toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
542 toggleDomStorage.setChecked(domStorageEnabled);
543 toggleSaveFormData.setChecked(saveFormDataEnabled);
545 // Set the status of the additional app bar icons. The default is `false`.
546 if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
547 toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
548 toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
549 toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
550 } else { //Do not display the additional icons.
551 toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
552 toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
553 toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
560 public boolean onPrepareOptionsMenu(Menu menu) {
561 // Only enable Third-Party Cookies if SDK >= 21 and First-Party Cookies are enabled.
562 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
563 if ((Build.VERSION.SDK_INT >= 21) && firstPartyCookiesEnabled) {
564 toggleThirdPartyCookies.setEnabled(true);
566 toggleThirdPartyCookies.setEnabled(false);
569 // Enable DOM Storage if JavaScript is enabled.
570 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
571 toggleDomStorage.setEnabled(javaScriptEnabled);
573 // Enable Clear Cookies if there are any.
574 MenuItem clearCookies = menu.findItem(R.id.clearCookies);
575 clearCookies.setEnabled(cookieManager.hasCookies());
577 // Enable Clear Form Data is there is any.
578 MenuItem clearFormData = menu.findItem(R.id.clearFormData);
579 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
580 clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
582 // Initialize font size variables.
583 int fontSize = mainWebView.getSettings().getTextZoom();
584 String fontSizeTitle;
585 MenuItem selectedFontSizeMenuItem;
587 // Prepare the font size title and current size menu item.
590 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
591 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
595 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
596 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
600 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
601 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
605 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
606 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
610 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
611 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
615 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
616 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
620 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
621 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
625 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
626 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
630 // Set the font size title and select the current size menu item.
631 MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
632 fontSizeMenuItem.setTitle(fontSizeTitle);
633 selectedFontSizeMenuItem.setChecked(true);
635 // Only show `Refresh` if `swipeToRefresh` is disabled.
636 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
637 refreshMenuItem.setVisible(!swipeToRefreshEnabled);
639 // Run all the other default commands.
640 super.onPrepareOptionsMenu(menu);
642 // `return true` displays the menu.
647 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
648 @SuppressLint("SetJavaScriptEnabled")
649 // removeAllCookies is deprecated, but it is required for API < 21.
650 @SuppressWarnings("deprecation")
651 public boolean onOptionsItemSelected(MenuItem menuItem) {
652 int menuItemId = menuItem.getItemId();
654 // Set the commands that relate to the menu entries.
655 switch (menuItemId) {
656 case R.id.toggleJavaScript:
657 // Switch the status of javaScriptEnabled.
658 javaScriptEnabled = !javaScriptEnabled;
660 // Apply the new JavaScript status.
661 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
663 // Update the privacy icon.
664 updatePrivacyIcons(privacyBrowserActivity);
666 // Display a `Snackbar`.
667 if (javaScriptEnabled) {
668 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
670 if (firstPartyCookiesEnabled) {
671 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
673 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
677 // Reload the WebView.
678 mainWebView.reload();
681 case R.id.toggleFirstPartyCookies:
682 // Switch the status of firstPartyCookiesEnabled.
683 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
685 // Update the menu checkbox.
686 menuItem.setChecked(firstPartyCookiesEnabled);
688 // Apply the new cookie status.
689 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
691 // Update the privacy icon.
692 updatePrivacyIcons(privacyBrowserActivity);
694 // Display a `Snackbar`.
695 if (firstPartyCookiesEnabled) {
696 Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
698 if (javaScriptEnabled) {
699 Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
701 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
705 // Reload the WebView.
706 mainWebView.reload();
709 case R.id.toggleThirdPartyCookies:
710 if (Build.VERSION.SDK_INT >= 21) {
711 // Switch the status of thirdPartyCookiesEnabled.
712 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
714 // Update the menu checkbox.
715 menuItem.setChecked(thirdPartyCookiesEnabled);
717 // Apply the new cookie status.
718 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
720 // Display a `Snackbar`.
721 if (thirdPartyCookiesEnabled) {
722 Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
724 Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
727 // Reload the WebView.
728 mainWebView.reload();
729 } // Else do nothing because SDK < 21.
732 case R.id.toggleDomStorage:
733 // Switch the status of domStorageEnabled.
734 domStorageEnabled = !domStorageEnabled;
736 // Update the menu checkbox.
737 menuItem.setChecked(domStorageEnabled);
739 // Apply the new DOM Storage status.
740 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
742 // Display a `Snackbar`.
743 if (domStorageEnabled) {
744 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
746 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
749 // Reload the WebView.
750 mainWebView.reload();
753 case R.id.toggleSaveFormData:
754 // Switch the status of saveFormDataEnabled.
755 saveFormDataEnabled = !saveFormDataEnabled;
757 // Update the menu checkbox.
758 menuItem.setChecked(saveFormDataEnabled);
760 // Apply the new form data status.
761 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
763 // Display a `Snackbar`.
764 if (saveFormDataEnabled) {
765 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
767 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
770 // Reload the WebView.
771 mainWebView.reload();
774 case R.id.clearCookies:
775 if (Build.VERSION.SDK_INT < 21) {
776 cookieManager.removeAllCookie();
778 cookieManager.removeAllCookies(null);
780 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
783 case R.id.clearDomStorage:
784 WebStorage webStorage = WebStorage.getInstance();
785 webStorage.deleteAllData();
786 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
789 case R.id.clearFormData:
790 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
791 mainWebViewDatabase.clearFormData();
792 mainWebView.reload();
795 case R.id.fontSizeFiftyPercent:
796 mainWebView.getSettings().setTextZoom(50);
799 case R.id.fontSizeSeventyFivePercent:
800 mainWebView.getSettings().setTextZoom(75);
803 case R.id.fontSizeOneHundredPercent:
804 mainWebView.getSettings().setTextZoom(100);
807 case R.id.fontSizeOneHundredTwentyFivePercent:
808 mainWebView.getSettings().setTextZoom(125);
811 case R.id.fontSizeOneHundredFiftyPercent:
812 mainWebView.getSettings().setTextZoom(150);
815 case R.id.fontSizeOneHundredSeventyFivePercent:
816 mainWebView.getSettings().setTextZoom(175);
819 case R.id.fontSizeTwoHundredPercent:
820 mainWebView.getSettings().setTextZoom(200);
824 Intent shareIntent = new Intent();
825 shareIntent.setAction(Intent.ACTION_SEND);
826 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
827 shareIntent.setType("text/plain");
828 startActivity(Intent.createChooser(shareIntent, "Share URL"));
831 case R.id.addToHomescreen:
832 // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `@string/create_shortcut`.
833 DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
834 createHomeScreenShortcutDialogFragment.show(getFragmentManager(), getResources().getString(R.string.create_shortcut));
836 //Everything else will be handled by `CreateHomeScreenShortcut` and the associated listener below.
840 mainWebView.reload();
844 // Don't consume the event.
845 return super.onOptionsItemSelected(menuItem);
850 // removeAllCookies is deprecated, but it is required for API < 21.
851 @SuppressWarnings("deprecation")
852 public boolean onNavigationItemSelected(MenuItem menuItem) {
853 int menuItemId = menuItem.getItemId();
855 switch (menuItemId) {
857 mainWebView.loadUrl(homepage, customHeaders);
861 if (mainWebView.canGoBack()) {
862 mainWebView.goBack();
867 if (mainWebView.canGoForward()) {
868 mainWebView.goForward();
873 // Launch BookmarksActivity.
874 Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
875 startActivity(bookmarksIntent);
879 // Launch the system Download Manager.
880 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
882 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
883 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
885 startActivity(downloadManagerIntent);
889 // Launch `SettingsActivity`.
890 Intent settingsIntent = new Intent(this, SettingsActivity.class);
891 startActivity(settingsIntent);
895 // Launch `GuideActivity`.
896 Intent guideIntent = new Intent(this, GuideActivity.class);
897 startActivity(guideIntent);
901 // Launch `AboutActivity`.
902 Intent aboutIntent = new Intent(this, AboutActivity.class);
903 startActivity(aboutIntent);
906 case R.id.clearAndExit:
907 // Clear cookies. The commands changed slightly in API 21.
908 if (Build.VERSION.SDK_INT >= 21) {
909 cookieManager.removeAllCookies(null);
911 cookieManager.removeAllCookie();
914 // Clear DOM storage.
915 WebStorage domStorage = WebStorage.getInstance();
916 domStorage.deleteAllData();
919 WebViewDatabase formData = WebViewDatabase.getInstance(this);
920 formData.clearFormData();
922 // Clear cache. The argument of "true" includes disk files.
923 mainWebView.clearCache(true);
925 // Clear the back/forward history.
926 mainWebView.clearHistory();
928 // Clear any SSL certificate preferences.
929 MainWebViewActivity.mainWebView.clearSslPreferences();
931 // Clear `formattedUrlString`.
932 formattedUrlString = null;
934 // Clear `customHeaders`.
935 customHeaders.clear();
937 // Destroy the internal state of the webview.
938 mainWebView.destroy();
940 // Close Privacy Browser. finishAndRemoveTask also removes Privacy Browser from the recent app list.
941 if (Build.VERSION.SDK_INT >= 21) {
942 finishAndRemoveTask();
952 // Close the navigation drawer.
953 drawerLayout.closeDrawer(GravityCompat.START);
958 public void onPostCreate(Bundle savedInstanceState) {
959 super.onPostCreate(savedInstanceState);
961 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
962 drawerToggle.syncState();
966 public void onConfigurationChanged(Configuration newConfig) {
967 super.onConfigurationChanged(newConfig);
969 // Reload the ad if this is the free flavor.
970 BannerAd.reloadAfterRotate(adView, privacyBrowserContext, getString(R.string.ad_id));
972 // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor by BannerAd.reloadAfterRotate().
973 adView = findViewById(R.id.adView);
975 // `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: https://code.google.com/p/android/issues/detail?id=20493#c8
976 invalidateOptionsMenu();
980 public void onCreateHomeScreenShortcut(DialogFragment dialogFragment) {
981 // Get shortcutNameEditText from the alert dialog.
982 EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
984 // Create the bookmark shortcut based on formattedUrlString.
985 Intent bookmarkShortcut = new Intent();
986 bookmarkShortcut.setAction(Intent.ACTION_VIEW);
987 bookmarkShortcut.setData(Uri.parse(formattedUrlString));
989 // Place the bookmark shortcut on the home screen.
990 Intent placeBookmarkShortcut = new Intent();
991 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
992 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
993 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
994 placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
995 sendBroadcast(placeBookmarkShortcut);
999 public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
1000 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
1001 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
1003 // Get the file name from `dialogFragment`.
1004 EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
1005 String fileName = downloadFileNameEditText.getText().toString();
1007 // Set the download save in the the `DIRECTORY_DOWNLOADS`using `fileName`.
1008 // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
1009 downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
1011 // Allow `MediaScanner` to index the download if it is a media file.
1012 downloadRequest.allowScanningByMediaScanner();
1014 // Add the URL as the description for the download.
1015 downloadRequest.setDescription(downloadUrl);
1017 // Show the download notification after the download is completed.
1018 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1020 // Initiate the download and display a Snackbar.
1021 downloadManager.enqueue(downloadRequest);
1024 public void viewSslCertificate(View view) {
1025 // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
1026 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate();
1027 viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
1031 public void onSslErrorCancel() {
1032 sslErrorHandler.cancel();
1036 public void onSslErrorProceed() {
1037 sslErrorHandler.proceed();
1040 // Override onBackPressed to handle the navigation drawer and mainWebView.
1042 public void onBackPressed() {
1043 final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
1045 // Close the navigation drawer if it is available. GravityCompat.START is the drawer on the left on Left-to-Right layout text.
1046 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1047 drawerLayout.closeDrawer(GravityCompat.START);
1049 // Load the previous URL if available.
1050 assert mainWebView != null; //This assert removes the incorrect warning in Android Studio on the following line that mainWebView might be null.
1051 if (mainWebView.canGoBack()) {
1052 mainWebView.goBack();
1054 // Pass onBackPressed to the system.
1055 super.onBackPressed();
1061 public void onPause() {
1062 // We need to pause the adView or it will continue to consume resources in the background on the free flavor.
1063 BannerAd.pauseAd(adView);
1069 public void onResume() {
1072 // We need to resume the adView for the free flavor.
1073 BannerAd.resumeAd(adView);
1076 private void loadUrlFromTextBox() throws UnsupportedEncodingException {
1077 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
1078 String unformattedUrlString = urlTextBox.getText().toString().trim();
1080 URL unformattedUrl = null;
1081 Uri.Builder formattedUri = new Uri.Builder();
1083 // Check to see if unformattedUrlString is a valid URL. Otherwise, convert it into a Duck Duck Go search.
1084 if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
1085 // Add http:// at the beginning if it is missing. Otherwise the app will segfault.
1086 if (!unformattedUrlString.startsWith("http")) {
1087 unformattedUrlString = "http://" + unformattedUrlString;
1090 // Convert unformattedUrlString to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components.
1092 unformattedUrl = new URL(unformattedUrlString);
1093 } catch (MalformedURLException e) {
1094 e.printStackTrace();
1097 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1098 final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1099 final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1100 final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1101 final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1102 final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1104 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1105 formattedUrlString = formattedUri.build().toString();
1107 // Sanitize the search input and convert it to a DuckDuckGo search.
1108 final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1110 // Use the correct search URL based on javaScriptEnabled.
1111 if (javaScriptEnabled) {
1112 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1113 } else { // JavaScript is disabled.
1114 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1118 mainWebView.loadUrl(formattedUrlString, customHeaders);
1120 // Hides the keyboard so we can see the webpage.
1121 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
1122 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1125 public static void updatePrivacyIcons(Activity activity) {
1126 // Get handles for the icons.
1127 MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript);
1128 MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies);
1129 MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage);
1130 MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData);
1132 // Update `privacyIcon`.
1133 if (javaScriptEnabled) { // JavaScript is enabled.
1134 privacyIcon.setIcon(R.drawable.javascript_enabled);
1135 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
1136 privacyIcon.setIcon(R.drawable.warning);
1137 } else { // All the dangerous features are disabled.
1138 privacyIcon.setIcon(R.drawable.privacy_mode);
1141 // Update `firstPartyCookiesIcon`.
1142 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
1143 firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled);
1144 } else { // First-party cookies are disabled.
1145 firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
1148 // Update `domStorageIcon`.
1149 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage is enabled.
1150 domStorageIcon.setIcon(R.drawable.dom_storage_enabled);
1151 } else if (javaScriptEnabled){ // JavaScript is enabled but DOM storage is disabled.
1152 domStorageIcon.setIcon(R.drawable.dom_storage_disabled);
1153 } else { // JavaScript is disabled, so DOM storage is ghosted.
1154 domStorageIcon.setIcon(R.drawable.dom_storage_ghosted);
1157 // Update `formDataIcon`.
1158 if (saveFormDataEnabled) { // Form data is enabled.
1159 formDataIcon.setIcon(R.drawable.form_data_enabled);
1160 } else { // Form data is disabled.
1161 formDataIcon.setIcon(R.drawable.form_data_disabled);
1164 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
1165 ActivityCompat.invalidateOptionsMenu(activity);