]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebView.java
Use GMS (Google Mobile Services) to update OpenSSL on API 19. Possible fix for https...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebView.java
1 /**
2  * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.activities;
21
22 import android.annotation.SuppressLint;
23 import android.app.DialogFragment;
24 import android.app.DownloadManager;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.res.Configuration;
29 import android.graphics.Bitmap;
30 import android.graphics.drawable.BitmapDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.net.http.SslCertificate;
34 import android.net.http.SslError;
35 import android.os.Build;
36 import android.os.Bundle;
37 import android.preference.PreferenceManager;
38 import android.print.PrintDocumentAdapter;
39 import android.print.PrintManager;
40 import android.support.annotation.NonNull;
41 import android.support.design.widget.NavigationView;
42 import android.support.design.widget.Snackbar;
43 import android.support.v4.app.ActivityCompat;
44 import android.support.v4.content.ContextCompat;
45 import android.support.v4.view.GravityCompat;
46 import android.support.v4.widget.DrawerLayout;
47 import android.support.v4.widget.SwipeRefreshLayout;
48 import android.support.v7.app.ActionBar;
49 import android.support.v7.app.ActionBarDrawerToggle;
50 import android.support.v7.app.AppCompatActivity;
51 import android.support.v7.app.AppCompatDialogFragment;
52 import android.support.v7.widget.Toolbar;
53 import android.text.Editable;
54 import android.text.TextWatcher;
55 import android.util.Log;
56 import android.util.Patterns;
57 import android.view.ContextMenu;
58 import android.view.KeyEvent;
59 import android.view.Menu;
60 import android.view.MenuItem;
61 import android.view.View;
62 import android.view.inputmethod.InputMethodManager;
63 import android.webkit.CookieManager;
64 import android.webkit.DownloadListener;
65 import android.webkit.SslErrorHandler;
66 import android.webkit.WebBackForwardList;
67 import android.webkit.WebChromeClient;
68 import android.webkit.WebStorage;
69 import android.webkit.WebView;
70 import android.webkit.WebViewClient;
71 import android.webkit.WebViewDatabase;
72 import android.widget.EditText;
73 import android.widget.FrameLayout;
74 import android.widget.ImageView;
75 import android.widget.LinearLayout;
76 import android.widget.ProgressBar;
77 import android.widget.RelativeLayout;
78 import android.widget.TextView;
79
80 // These three GMS (Google Mobile Services) lines need to be removed for F-Droid to build correctly.
81 import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
82 import com.google.android.gms.common.GooglePlayServicesRepairableException;
83 import com.google.android.gms.security.ProviderInstaller;
84
85 import com.stoutner.privacybrowser.BannerAd;
86 import com.stoutner.privacybrowser.R;
87 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
88 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcut;
89 import com.stoutner.privacybrowser.dialogs.DownloadFile;
90 import com.stoutner.privacybrowser.dialogs.DownloadImage;
91 import com.stoutner.privacybrowser.dialogs.SslCertificateError;
92 import com.stoutner.privacybrowser.dialogs.UrlHistory;
93 import com.stoutner.privacybrowser.dialogs.ViewSslCertificate;
94
95 import java.io.UnsupportedEncodingException;
96 import java.net.MalformedURLException;
97 import java.net.URL;
98 import java.net.URLEncoder;
99 import java.util.HashMap;
100 import java.util.Map;
101
102 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
103 public class MainWebView extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener,
104         SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener, DownloadImage.DownloadImageListener, UrlHistory.UrlHistoryListener {
105
106     // `appBar` is public static so it can be accessed from `OrbotProxyHelper`.
107     // It is also used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
108     public static ActionBar appBar;
109
110     // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `Bookmarks`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
111     // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
112     public static Bitmap favoriteIcon;
113
114     // `formattedUrlString` is public static so it can be accessed from `Bookmarks`.
115     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
116     public static String formattedUrlString;
117
118     // `sslCertificate` is public static so it can be accessed from `ViewSslCertificate`.  It is also used in `onCreate()`.
119     public static SslCertificate sslCertificate;
120
121
122     // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`.
123     private WebView mainWebView;
124
125     // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
126     private SwipeRefreshLayout swipeRefreshLayout;
127
128     // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, and `onRestart()`.
129     private CookieManager cookieManager;
130
131     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrlFromTextBox()`.
132     private final Map<String, String> customHeaders = new HashMap<>();
133
134     // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`.
135     // It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`.
136     private Boolean javaScriptEnabled;
137
138     // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
139     private boolean firstPartyCookiesEnabled;
140
141     // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
142     private boolean thirdPartyCookiesEnabled;
143
144     // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
145     private boolean domStorageEnabled;
146
147     // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
148     private boolean saveFormDataEnabled;
149
150     // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applySettings()`.
151     private boolean swipeToRefreshEnabled;
152
153     // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applySettings()`.
154     private String homepage;
155
156     // `javaScriptDisabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
157     private String javaScriptDisabledSearchURL;
158
159     // `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
160     private String javaScriptEnabledSearchURL;
161
162     // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
163     private Menu mainMenu;
164
165     // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
166     private ActionBarDrawerToggle drawerToggle;
167
168     // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
169     private DrawerLayout drawerLayout;
170
171     // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
172     private EditText urlTextBox;
173
174     // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
175     private View adView;
176
177     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
178     private SslErrorHandler sslErrorHandler;
179
180     // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
181     private EditText findOnPageEditText;
182
183     // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
184     private InputMethodManager inputMethodManager;
185
186     @Override
187     // 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.
188     @SuppressLint("SetJavaScriptEnabled")
189     protected void onCreate(Bundle savedInstanceState) {
190         super.onCreate(savedInstanceState);
191         setContentView(R.layout.main_coordinatorlayout);
192
193         // API 19 uses an old version of OpenSSL, which exposes users to man-in-the-middle attacks.
194         if (Build.VERSION.SDK_INT == 19) {
195             try {
196                 // Update OpenSSL using Google Play Services.  This command must be removed for the build to succeed on F-Droid.
197                 ProviderInstaller.installIfNeeded(this);
198             } catch (GooglePlayServicesRepairableException exception) {
199                 Log.i("Privacy Browser", "OpenSSL needs to be updated");
200             } catch (GooglePlayServicesNotAvailableException exception) {
201                 Log.i("Privacy Browser", "OpenSSL cannot be updated because Google Play Services are not available");
202             }
203         }
204
205         // Get a handle for `inputMethodManager`.
206         inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
207
208         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
209         Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
210         setSupportActionBar(supportAppBar);
211         appBar = getSupportActionBar();
212
213         // This is needed to get rid of the Android Studio warning that appBar might be null.
214         assert appBar != null;
215
216         // Add the custom url_app_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
217         appBar.setCustomView(R.layout.url_app_bar);
218         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
219
220         // Set the "go" button on the keyboard to load the URL in urlTextBox.
221         urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
222         urlTextBox.setOnKeyListener(new View.OnKeyListener() {
223             @Override
224             public boolean onKey(View v, int keyCode, KeyEvent event) {
225                 // If the event is a key-down event on the `enter` button, load the URL.
226                 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
227                     // Load the URL into the mainWebView and consume the event.
228                     try {
229                         loadUrlFromTextBox();
230                     } catch (UnsupportedEncodingException e) {
231                         e.printStackTrace();
232                     }
233                     // If the enter key was pressed, consume the event.
234                     return true;
235                 } else {
236                     // If any other key was pressed, do not consume the event.
237                     return false;
238                 }
239             }
240         });
241
242         // Get handles for `fullScreenVideoFrameLayout`, `mainWebView`, and `find_on_page_edittext`.
243         final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
244         mainWebView = (WebView) findViewById(R.id.mainWebView);
245         findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext);
246
247         // Update `findOnPageCountTextView`.
248         mainWebView.setFindListener(new WebView.FindListener() {
249             // Get a handle for `findOnPageCountTextView`.
250             TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
251
252             @Override
253             public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
254                 if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
255                     // Set `findOnPageCountTextView` to `0/0`.
256                     findOnPageCountTextView.setText(R.string.zero_of_zero);
257                 } else if (isDoneCounting) {  // There are matches.
258                     // `activeMatchOrdinal` is zero-based.
259                     int activeMatch = activeMatchOrdinal + 1;
260
261                     // Set `findOnPageCountTextView`.
262                     findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches);
263                 }
264             }
265         });
266
267         // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
268         findOnPageEditText.addTextChangedListener(new TextWatcher() {
269             @Override
270             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
271                 // Do nothing.
272             }
273
274             @Override
275             public void onTextChanged(CharSequence s, int start, int before, int count) {
276                 // Do nothing.
277             }
278
279             @Override
280             public void afterTextChanged(Editable s) {
281                 // Search for the text in `mainWebView`.
282                 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
283             }
284         });
285
286         // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
287         findOnPageEditText.setOnKeyListener(new View.OnKeyListener() {
288             @Override
289             public boolean onKey(View v, int keyCode, KeyEvent event) {
290                 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
291                     // Hide the soft keyboard.  `0` indicates no additional flags.
292                     inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
293
294                     // Consume the event.
295                     return true;
296                 } else {  // A different key was pressed.
297                     // Do not consume the event.
298                     return false;
299                 }
300             }
301         });
302
303         // Implement swipe to refresh
304         swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
305         swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
306         swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
307             @Override
308             public void onRefresh() {
309                 mainWebView.reload();
310             }
311         });
312
313         // Create the navigation drawer.
314         drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
315         // `DrawerTitle` identifies the drawer in accessibility mode.
316         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
317
318         // Listen for touches on the navigation menu.
319         final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
320         navigationView.setNavigationItemSelectedListener(this);
321
322         // Get handles for `navigationMenu` and the back and forward menu items.  The menu is zero-based, so item 1 and 2 and the second and third items in the menu.
323         final Menu navigationMenu = navigationView.getMenu();
324         final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
325         final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
326         final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
327
328         // The `DrawerListener` allows us to update the Navigation Menu.
329         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
330             @Override
331             public void onDrawerSlide(View drawerView, float slideOffset) {
332             }
333
334             @Override
335             public void onDrawerOpened(View drawerView) {
336             }
337
338             @Override
339             public void onDrawerClosed(View drawerView) {
340             }
341
342             @Override
343             public void onDrawerStateChanged(int newState) {
344                 // Update the `Back`, `Forward`, and `History` menu items every time the drawer opens.
345                 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
346                 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
347                 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
348
349                 // Hide the keyboard so we can see the navigation menu.  `0` indicates no additional flags.
350                 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
351             }
352         });
353
354         // drawerToggle creates the hamburger icon at the start of the AppBar.
355         drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
356
357         mainWebView.setWebViewClient(new WebViewClient() {
358             // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
359             // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
360             @SuppressWarnings("deprecation")
361             @Override
362             public boolean shouldOverrideUrlLoading(WebView view, String url) {
363                 // Use an external email program if the link begins with "mailto:".
364                 if (url.startsWith("mailto:")) {
365                     // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
366                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
367
368                     // Parse the url and set it as the data for the `Intent`.
369                     emailIntent.setData(Uri.parse(url));
370
371                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
372                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
373
374                     // Make it so.
375                     startActivity(emailIntent);
376                     return true;
377                 } else {  // Load the URL in Privacy Browser.
378                     mainWebView.loadUrl(url, customHeaders);
379                     return true;
380                 }
381             }
382
383             // Update the URL in urlTextBox when the page starts to load.
384             @Override
385             public void onPageStarted(WebView view, String url, Bitmap favicon) {
386                 // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
387                 formattedUrlString = url;
388
389                 // Display the loading URL is the URL text box.
390                 urlTextBox.setText(url);
391             }
392
393             // Update formattedUrlString and urlTextBox.  It is necessary to do this after the page finishes loading because the final URL can change during load.
394             @Override
395             public void onPageFinished(WebView view, String url) {
396                 formattedUrlString = url;
397
398                 // Only update urlTextBox if the user is not typing in it.
399                 if (!urlTextBox.hasFocus()) {
400                     urlTextBox.setText(formattedUrlString);
401                 }
402
403                 // Store the SSL certificate so it can be accessed from `ViewSslCertificate`.
404                 sslCertificate = mainWebView.getCertificate();
405             }
406
407             // Handle SSL Certificate errors.
408             @Override
409             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
410                 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
411                 sslErrorHandler = handler;
412
413                 // Display the SSL error `AlertDialog`.
414                 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error);
415                 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
416             }
417         });
418
419         mainWebView.setWebChromeClient(new WebChromeClient() {
420             // Update the progress bar when a page is loading.
421             @Override
422             public void onProgressChanged(WebView view, int progress) {
423                 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
424                 progressBar.setProgress(progress);
425                 if (progress < 100) {
426                     progressBar.setVisibility(View.VISIBLE);
427                 } else {
428                     progressBar.setVisibility(View.GONE);
429
430                     //Stop the `SwipeToRefresh` indicator if it is running
431                     swipeRefreshLayout.setRefreshing(false);
432                 }
433             }
434
435             // Set the favorite icon when it changes.
436             @Override
437             public void onReceivedIcon(WebView view, Bitmap icon) {
438                 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
439                 favoriteIcon = icon;
440
441                 // Place the favorite icon in the appBar.
442                 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
443                 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
444             }
445
446             // Enter full screen video
447             @Override
448             public void onShowCustomView(View view, CustomViewCallback callback) {
449                 appBar.hide();
450
451                 // Show the fullScreenVideoFrameLayout.
452                 fullScreenVideoFrameLayout.addView(view);
453                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
454
455                 // Hide the mainWebView.
456                 mainWebView.setVisibility(View.GONE);
457
458                 // Hide the ad if this is the free flavor.
459                 BannerAd.hideAd(adView);
460
461                 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
462                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
463                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
464                  */
465                 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
466             }
467
468             // Exit full screen video
469             public void onHideCustomView() {
470                 appBar.show();
471
472                 // Show the mainWebView.
473                 mainWebView.setVisibility(View.VISIBLE);
474
475                 // Show the ad if this is the free flavor.
476                 BannerAd.showAd(adView);
477
478                 // Hide the fullScreenVideoFrameLayout.
479                 fullScreenVideoFrameLayout.removeAllViews();
480                 fullScreenVideoFrameLayout.setVisibility(View.GONE);
481             }
482         });
483
484         // Register `mainWebView` for a context menu.  This is used to see link targets and download images.
485         registerForContextMenu(mainWebView);
486
487         // Allow the downloading of files.
488         mainWebView.setDownloadListener(new DownloadListener() {
489             @Override
490             public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
491                 // Show the `DownloadFile` `AlertDialog` and name this instance `@string/download`.
492                 AppCompatDialogFragment downloadFileDialogFragment = DownloadFile.fromUrl(url, contentDisposition, contentLength);
493                 downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
494             }
495         });
496
497         // Allow pinch to zoom.
498         mainWebView.getSettings().setBuiltInZoomControls(true);
499
500         // Hide zoom controls.
501         mainWebView.getSettings().setDisplayZoomControls(false);
502
503         // Initialize cookieManager.
504         cookieManager = CookieManager.getInstance();
505
506         // 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).
507         customHeaders.put("X-Requested-With", "");
508
509         // Initialize the default preference values the first time the program is run.
510         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
511
512         // Apply the settings from the shared preferences.
513         applySettings();
514
515         // Get the intent information that started the app.
516         final Intent intent = getIntent();
517
518         if (intent.getData() != null) {
519             // Get the intent data and convert it to a string.
520             final Uri intentUriData = intent.getData();
521             formattedUrlString = intentUriData.toString();
522         }
523
524         // If formattedUrlString is null assign the homepage to it.
525         if (formattedUrlString == null) {
526             formattedUrlString = homepage;
527         }
528
529         // Load the initial website.
530         mainWebView.loadUrl(formattedUrlString, customHeaders);
531
532         // If the favorite icon is null, load the default.
533         if (favoriteIcon == null) {
534             // We have to use `ContextCompat` until API >= 21.
535             Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
536             BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
537             favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
538         }
539
540         // Initialize AdView for the free flavor and request an ad.  If this is not the free flavor BannerAd.requestAd() does nothing.
541         adView = findViewById(R.id.adView);
542         BannerAd.requestAd(adView);
543     }
544
545
546     @Override
547     protected void onNewIntent(Intent intent) {
548         // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
549         setIntent(intent);
550
551         if (intent.getData() != null) {
552             // Get the intent data and convert it to a string.
553             final Uri intentUriData = intent.getData();
554             formattedUrlString = intentUriData.toString();
555         }
556
557         // Close the navigation drawer if it is open.
558         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
559             drawerLayout.closeDrawer(GravityCompat.START);
560         }
561
562         // Load the website.
563         mainWebView.loadUrl(formattedUrlString, customHeaders);
564
565         // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
566         mainWebView.requestFocus();
567     }
568
569     @Override
570     public boolean onCreateOptionsMenu(Menu menu) {
571         // Inflate the menu; this adds items to the action bar if it is present.
572         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
573
574         // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
575         mainMenu = menu;
576
577         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
578         updatePrivacyIcons(false);
579
580         // Get handles for the menu items.
581         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
582         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
583         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
584         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
585
586         // Only display third-Party Cookies if SDK >= 21
587         toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21);
588
589         // Get the shared preference values.  `this` references the current context.
590         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
591
592         // Set the status of the additional app bar icons.  The default is `false`.
593         if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
594             toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
595             toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
596             toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
597         } else { //Do not display the additional icons.
598             toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
599             toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
600             toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
601         }
602
603         return true;
604     }
605
606     @Override
607     public boolean onPrepareOptionsMenu(Menu menu) {
608         // Get handles for the menu items.
609         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
610         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
611         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
612         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
613         MenuItem clearCookies = menu.findItem(R.id.clearCookies);
614         MenuItem clearFormData = menu.findItem(R.id.clearFormData);
615         MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
616
617         // Set the status of the menu item checkboxes.
618         toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
619         toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
620         toggleDomStorage.setChecked(domStorageEnabled);
621         toggleSaveFormData.setChecked(saveFormDataEnabled);
622
623         // Enable third-party cookies if first-party cookies are enabled.
624         toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled);
625
626         // Enable DOM Storage if JavaScript is enabled.
627         toggleDomStorage.setEnabled(javaScriptEnabled);
628
629         // Enable Clear Cookies if there are any.
630         clearCookies.setEnabled(cookieManager.hasCookies());
631
632         // Enable Clear Form Data is there is any.
633         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
634         clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
635
636         // Only show `Refresh` if `swipeToRefresh` is disabled.
637         refreshMenuItem.setVisible(!swipeToRefreshEnabled);
638
639         // Initialize font size variables.
640         int fontSize = mainWebView.getSettings().getTextZoom();
641         String fontSizeTitle;
642         MenuItem selectedFontSizeMenuItem;
643
644         // Prepare the font size title and current size menu item.
645         switch (fontSize) {
646             case 50:
647                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
648                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
649                 break;
650
651             case 75:
652                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
653                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
654                 break;
655
656             case 100:
657                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
658                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
659                 break;
660
661             case 125:
662                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
663                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
664                 break;
665
666             case 150:
667                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
668                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
669                 break;
670
671             case 175:
672                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
673                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
674                 break;
675
676             case 200:
677                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
678                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
679                 break;
680
681             default:
682                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
683                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
684                 break;
685         }
686
687         // Set the font size title and select the current size menu item.
688         MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
689         fontSizeMenuItem.setTitle(fontSizeTitle);
690         selectedFontSizeMenuItem.setChecked(true);
691
692         // Run all the other default commands.
693         super.onPrepareOptionsMenu(menu);
694
695         // `return true` displays the menu.
696         return true;
697     }
698
699     @Override
700     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
701     @SuppressLint("SetJavaScriptEnabled")
702     // removeAllCookies is deprecated, but it is required for API < 21.
703     @SuppressWarnings("deprecation")
704     public boolean onOptionsItemSelected(MenuItem menuItem) {
705         int menuItemId = menuItem.getItemId();
706
707         // Set the commands that relate to the menu entries.
708         switch (menuItemId) {
709             case R.id.toggleJavaScript:
710                 // Switch the status of javaScriptEnabled.
711                 javaScriptEnabled = !javaScriptEnabled;
712
713                 // Apply the new JavaScript status.
714                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
715
716                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
717                 updatePrivacyIcons(true);
718
719                 // Display a `Snackbar`.
720                 if (javaScriptEnabled) {  // JavaScrip is enabled.
721                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
722                 } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
723                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
724                 } else {  // Privacy mode.
725                     Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
726                 }
727
728                 // Reload the WebView.
729                 mainWebView.reload();
730                 return true;
731
732             case R.id.toggleFirstPartyCookies:
733                 // Switch the status of firstPartyCookiesEnabled.
734                 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
735
736                 // Update the menu checkbox.
737                 menuItem.setChecked(firstPartyCookiesEnabled);
738
739                 // Apply the new cookie status.
740                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
741
742                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
743                 updatePrivacyIcons(true);
744
745                 // Display a `Snackbar`.
746                 if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
747                     Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
748                 } else if (javaScriptEnabled){  // JavaScript is still enabled.
749                     Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
750                 } else {  // Privacy mode.
751                     Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
752                 }
753
754                 // Reload the WebView.
755                 mainWebView.reload();
756                 return true;
757
758             case R.id.toggleThirdPartyCookies:
759                 if (Build.VERSION.SDK_INT >= 21) {
760                     // Switch the status of thirdPartyCookiesEnabled.
761                     thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
762
763                     // Update the menu checkbox.
764                     menuItem.setChecked(thirdPartyCookiesEnabled);
765
766                     // Apply the new cookie status.
767                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
768
769                     // Display a `Snackbar`.
770                     if (thirdPartyCookiesEnabled) {
771                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
772                     } else {
773                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
774                     }
775
776                     // Reload the WebView.
777                     mainWebView.reload();
778                 } // Else do nothing because SDK < 21.
779                 return true;
780
781             case R.id.toggleDomStorage:
782                 // Switch the status of domStorageEnabled.
783                 domStorageEnabled = !domStorageEnabled;
784
785                 // Update the menu checkbox.
786                 menuItem.setChecked(domStorageEnabled);
787
788                 // Apply the new DOM Storage status.
789                 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
790
791                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
792                 updatePrivacyIcons(true);
793
794                 // Display a `Snackbar`.
795                 if (domStorageEnabled) {
796                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
797                 } else {
798                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
799                 }
800
801                 // Reload the WebView.
802                 mainWebView.reload();
803                 return true;
804
805             case R.id.toggleSaveFormData:
806                 // Switch the status of saveFormDataEnabled.
807                 saveFormDataEnabled = !saveFormDataEnabled;
808
809                 // Update the menu checkbox.
810                 menuItem.setChecked(saveFormDataEnabled);
811
812                 // Apply the new form data status.
813                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
814
815                 // Display a `Snackbar`.
816                 if (saveFormDataEnabled) {
817                     Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
818                 } else {
819                     Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
820                 }
821
822                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
823                 updatePrivacyIcons(true);
824
825                 // Reload the WebView.
826                 mainWebView.reload();
827                 return true;
828
829             case R.id.clearCookies:
830                 if (Build.VERSION.SDK_INT < 21) {
831                     cookieManager.removeAllCookie();
832                 } else {
833                     cookieManager.removeAllCookies(null);
834                 }
835                 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
836                 return true;
837
838             case R.id.clearDomStorage:
839                 WebStorage webStorage = WebStorage.getInstance();
840                 webStorage.deleteAllData();
841                 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
842                 return true;
843
844             case R.id.clearFormData:
845                 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
846                 mainWebViewDatabase.clearFormData();
847                 mainWebView.reload();
848                 return true;
849
850             case R.id.fontSizeFiftyPercent:
851                 mainWebView.getSettings().setTextZoom(50);
852                 return true;
853
854             case R.id.fontSizeSeventyFivePercent:
855                 mainWebView.getSettings().setTextZoom(75);
856                 return true;
857
858             case R.id.fontSizeOneHundredPercent:
859                 mainWebView.getSettings().setTextZoom(100);
860                 return true;
861
862             case R.id.fontSizeOneHundredTwentyFivePercent:
863                 mainWebView.getSettings().setTextZoom(125);
864                 return true;
865
866             case R.id.fontSizeOneHundredFiftyPercent:
867                 mainWebView.getSettings().setTextZoom(150);
868                 return true;
869
870             case R.id.fontSizeOneHundredSeventyFivePercent:
871                 mainWebView.getSettings().setTextZoom(175);
872                 return true;
873
874             case R.id.fontSizeTwoHundredPercent:
875                 mainWebView.getSettings().setTextZoom(200);
876                 return true;
877
878             case R.id.find_on_page:
879                 // Hide the URL app bar.
880                 Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar);
881                 appBarToolbar.setVisibility(View.GONE);
882
883                 // Show the Find on Page `RelativeLayout`.
884                 LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
885                 findOnPageLinearLayout.setVisibility(View.VISIBLE);
886
887                 // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.
888                 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
889                 findOnPageEditText.postDelayed(new Runnable()
890                 {
891                     @Override
892                     public void run()
893                     {
894                         // Set the focus on `findOnPageEditText`.
895                         findOnPageEditText.requestFocus();
896
897                         // Display the keyboard.
898                         inputMethodManager.showSoftInput(findOnPageEditText, 0);
899                     }
900                 }, 200);
901                 return true;
902
903             case R.id.share:
904                 Intent shareIntent = new Intent();
905                 shareIntent.setAction(Intent.ACTION_SEND);
906                 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
907                 shareIntent.setType("text/plain");
908                 startActivity(Intent.createChooser(shareIntent, "Share URL"));
909                 return true;
910
911             case R.id.addToHomescreen:
912                 // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `R.string.create_shortcut`.
913                 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
914                 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut));
915
916                 //Everything else will be handled by `CreateHomeScreenShortcut` and the associated listener below.
917                 return true;
918
919             case R.id.print:
920                 // Get a `PrintManager` instance.
921                 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
922
923                 // Convert `mainWebView` to `printDocumentAdapter`.
924                 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
925
926                 // Print the document.  The print attributes are `null`.
927                 printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
928                 return true;
929
930             case R.id.refresh:
931                 mainWebView.reload();
932                 return true;
933
934             default:
935                 // Don't consume the event.
936                 return super.onOptionsItemSelected(menuItem);
937         }
938     }
939
940     // removeAllCookies is deprecated, but it is required for API < 21.
941     @SuppressWarnings("deprecation")
942     @Override
943     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
944         int menuItemId = menuItem.getItemId();
945
946         switch (menuItemId) {
947             case R.id.home:
948                 mainWebView.loadUrl(homepage, customHeaders);
949                 break;
950
951             case R.id.back:
952                 if (mainWebView.canGoBack()) {
953                     mainWebView.goBack();
954                 }
955                 break;
956
957             case R.id.forward:
958                 if (mainWebView.canGoForward()) {
959                     mainWebView.goForward();
960                 }
961                 break;
962
963             case R.id.history:
964                 // Gte the `WebBackForwardList`.
965                 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
966
967                 // Show the `UrlHistory` `AlertDialog` and name this instance `R.string.history`.  `this` is the `Context`.
968                 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistory.loadBackForwardList(this, webBackForwardList);
969                 urlHistoryDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.history));
970                 break;
971
972             case R.id.bookmarks:
973                 // Launch Bookmarks.
974                 Intent bookmarksIntent = new Intent(this, Bookmarks.class);
975                 startActivity(bookmarksIntent);
976                 break;
977
978             case R.id.downloads:
979                 // Launch the system Download Manager.
980                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
981
982                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
983                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
984
985                 startActivity(downloadManagerIntent);
986                 break;
987
988             case R.id.settings:
989                 // Launch `Settings`.
990                 Intent settingsIntent = new Intent(this, Settings.class);
991                 startActivity(settingsIntent);
992                 break;
993
994             case R.id.guide:
995                 // Launch `Guide`.
996                 Intent guideIntent = new Intent(this, Guide.class);
997                 startActivity(guideIntent);
998                 break;
999
1000             case R.id.about:
1001                 // Launch `About`.
1002                 Intent aboutIntent = new Intent(this, About.class);
1003                 startActivity(aboutIntent);
1004                 break;
1005
1006             case R.id.clearAndExit:
1007                 // Clear cookies.  The commands changed slightly in API 21.
1008                 if (Build.VERSION.SDK_INT >= 21) {
1009                     cookieManager.removeAllCookies(null);
1010                 } else {
1011                     cookieManager.removeAllCookie();
1012                 }
1013
1014                 // Clear DOM storage.
1015                 WebStorage domStorage = WebStorage.getInstance();
1016                 domStorage.deleteAllData();
1017
1018                 // Clear form data.
1019                 WebViewDatabase formData = WebViewDatabase.getInstance(this);
1020                 formData.clearFormData();
1021
1022                 // Clear cache.  The argument of "true" includes disk files.
1023                 mainWebView.clearCache(true);
1024
1025                 // Clear the back/forward history.
1026                 mainWebView.clearHistory();
1027
1028                 // Clear any SSL certificate preferences.
1029                 mainWebView.clearSslPreferences();
1030
1031                 // Clear `formattedUrlString`.
1032                 formattedUrlString = null;
1033
1034                 // Clear `customHeaders`.
1035                 customHeaders.clear();
1036
1037                 // Detach all views from `mainWebViewRelativeLayout`.
1038                 RelativeLayout mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.mainWebViewRelativeLayout);
1039                 mainWebViewRelativeLayout.removeAllViews();
1040
1041                 // Destroy the internal state of `mainWebView`.
1042                 mainWebView.destroy();
1043
1044                 // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
1045                 if (Build.VERSION.SDK_INT >= 21) {
1046                     finishAndRemoveTask();
1047                 } else {
1048                     finish();
1049                 }
1050
1051                 // Remove the terminated program from RAM.  The status code is `0`.
1052                 System.exit(0);
1053                 break;
1054
1055             default:
1056                 break;
1057         }
1058
1059         // Close the navigation drawer.
1060         drawerLayout.closeDrawer(GravityCompat.START);
1061         return true;
1062     }
1063
1064     @Override
1065     public void onPostCreate(Bundle savedInstanceState) {
1066         super.onPostCreate(savedInstanceState);
1067
1068         // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
1069         drawerToggle.syncState();
1070     }
1071
1072     @Override
1073     public void onConfigurationChanged(Configuration newConfig) {
1074         super.onConfigurationChanged(newConfig);
1075
1076         // Reload the ad if this is the free flavor.
1077         BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
1078
1079         // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor by BannerAd.reloadAfterRotate().
1080         adView = findViewById(R.id.adView);
1081
1082         // `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
1083         // ActivityCompat.invalidateOptionsMenu(this);
1084     }
1085
1086     @Override
1087     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
1088         // Store the `HitTestResult`.
1089         final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
1090
1091         // Create strings.
1092         final String imageUrl;
1093         final String linkUrl;
1094
1095         switch (hitTestResult.getType()) {
1096             // `SRC_ANCHOR_TYPE` is a link.
1097             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1098                 // Get the target URL.
1099                 linkUrl = hitTestResult.getExtra();
1100
1101                 // Set the target URL as the title of the `ContextMenu`.
1102                 menu.setHeaderTitle(linkUrl);
1103
1104                 // Add a `Load URL` button.
1105                 menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1106                     @Override
1107                     public boolean onMenuItemClick(MenuItem item) {
1108                         mainWebView.loadUrl(linkUrl, customHeaders);
1109                         return false;
1110                     }
1111                 });
1112
1113                 // Add a `Cancel` button, which by default closes the `ContextMenu`.
1114                 menu.add(R.string.cancel);
1115                 break;
1116
1117             case WebView.HitTestResult.EMAIL_TYPE:
1118                 // Get the target URL.
1119                 linkUrl = hitTestResult.getExtra();
1120
1121                 // Set the target URL as the title of the `ContextMenu`.
1122                 menu.setHeaderTitle(linkUrl);
1123
1124                 // Add a `Write Email` button.
1125                 menu.add(R.string.write_email).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1126                     @Override
1127                     public boolean onMenuItemClick(MenuItem item) {
1128                         // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1129                         Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1130
1131                         // Parse the url and set it as the data for the `Intent`.
1132                         emailIntent.setData(Uri.parse("mailto:" + linkUrl));
1133
1134                         // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
1135                         emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1136
1137                         // Make it so.
1138                         startActivity(emailIntent);
1139                         return false;
1140                     }
1141                 });
1142
1143                 // Add a `Cancel` button, which by default closes the `ContextMenu`.
1144                 menu.add(R.string.cancel);
1145                 break;
1146
1147             // `IMAGE_TYPE` is an image.
1148             case WebView.HitTestResult.IMAGE_TYPE:
1149                 // Get the image URL.
1150                 imageUrl = hitTestResult.getExtra();
1151
1152                 // Set the image URL as the title of the `ContextMenu`.
1153                 menu.setHeaderTitle(imageUrl);
1154
1155                 // Add a `View Image` button.
1156                 menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1157                     @Override
1158                     public boolean onMenuItemClick(MenuItem item) {
1159                         mainWebView.loadUrl(imageUrl, customHeaders);
1160                         return false;
1161                     }
1162                 });
1163
1164                 // Add a `Download Image` button.
1165                 menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1166                     @Override
1167                     public boolean onMenuItemClick(MenuItem item) {
1168                         // Show the `DownloadImage` `AlertDialog` and name this instance `@string/download`.
1169                         AppCompatDialogFragment downloadImageDialogFragment = DownloadImage.imageUrl(imageUrl);
1170                         downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
1171                         return false;
1172                     }
1173                 });
1174
1175                 // Add a `Cancel` button, which by default closes the `ContextMenu`.
1176                 menu.add(R.string.cancel);
1177                 break;
1178
1179
1180             // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
1181             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1182                 // Get the image URL.
1183                 imageUrl = hitTestResult.getExtra();
1184
1185                 // Set the image URL as the title of the `ContextMenu`.
1186                 menu.setHeaderTitle(imageUrl);
1187
1188                 // Add a `View Image` button.
1189                 menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1190                     @Override
1191                     public boolean onMenuItemClick(MenuItem item) {
1192                         mainWebView.loadUrl(imageUrl, customHeaders);
1193                         return false;
1194                     }
1195                 });
1196
1197                 // Add a `Download Image` button.
1198                 menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1199                     @Override
1200                     public boolean onMenuItemClick(MenuItem item) {
1201                         // Show the `DownloadImage` `AlertDialog` and name this instance `@string/download`.
1202                         AppCompatDialogFragment downloadImageDialogFragment = DownloadImage.imageUrl(imageUrl);
1203                         downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
1204                         return false;
1205                     }
1206                 });
1207
1208                 // Add a `Cancel` button, which by default closes the `ContextMenu`.
1209                 menu.add(R.string.cancel);
1210                 break;
1211         }
1212     }
1213
1214     @Override
1215     public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
1216         // Get shortcutNameEditText from the alert dialog.
1217         EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
1218
1219         // Create the bookmark shortcut based on formattedUrlString.
1220         Intent bookmarkShortcut = new Intent();
1221         bookmarkShortcut.setAction(Intent.ACTION_VIEW);
1222         bookmarkShortcut.setData(Uri.parse(formattedUrlString));
1223
1224         // Place the bookmark shortcut on the home screen.
1225         Intent placeBookmarkShortcut = new Intent();
1226         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
1227         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
1228         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
1229         placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1230         sendBroadcast(placeBookmarkShortcut);
1231     }
1232
1233     @Override
1234     public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
1235         // Get a handle for the system `DOWNLOAD_SERVICE`.
1236         DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
1237
1238         // Parse `imageUrl`.
1239         DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
1240
1241         // Get the file name from `dialogFragment`.
1242         EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name);
1243         String imageName = downloadImageNameEditText.getText().toString();
1244
1245         // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
1246         if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`.
1247             downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName);
1248         } else { // Only set the title using `imageName`.
1249             downloadRequest.setTitle(imageName);
1250         }
1251
1252         // Allow `MediaScanner` to index the download if it is a media file.
1253         downloadRequest.allowScanningByMediaScanner();
1254
1255         // Add the URL as the description for the download.
1256         downloadRequest.setDescription(imageUrl);
1257
1258         // Show the download notification after the download is completed.
1259         downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1260
1261         // Initiate the download.
1262         downloadManager.enqueue(downloadRequest);
1263     }
1264
1265     @Override
1266     public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
1267         // Get a handle for the system `DOWNLOAD_SERVICE`.
1268         DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
1269
1270         // Parse `downloadUrl`.
1271         DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
1272
1273         // Get the file name from `dialogFragment`.
1274         EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
1275         String fileName = downloadFileNameEditText.getText().toString();
1276
1277         // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
1278         if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `fileName`.
1279             downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
1280         } else { // Only set the title using `fileName`.
1281             downloadRequest.setTitle(fileName);
1282         }
1283
1284         // Allow `MediaScanner` to index the download if it is a media file.
1285         downloadRequest.allowScanningByMediaScanner();
1286
1287         // Add the URL as the description for the download.
1288         downloadRequest.setDescription(downloadUrl);
1289
1290         // Show the download notification after the download is completed.
1291         downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1292
1293         // Initiate the download.
1294         downloadManager.enqueue(downloadRequest);
1295     }
1296
1297     public void viewSslCertificate(View view) {
1298         // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
1299         DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate();
1300         viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
1301     }
1302
1303     @Override
1304     public void onSslErrorCancel() {
1305         sslErrorHandler.cancel();
1306     }
1307
1308     @Override
1309     public void onSslErrorProceed() {
1310         sslErrorHandler.proceed();
1311     }
1312
1313     @Override
1314     public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
1315         // Load the history entry.
1316         mainWebView.goBackOrForward(moveBackOrForwardSteps);
1317     }
1318
1319     @Override
1320     public void onClearHistory() {
1321         // Clear the history.
1322         mainWebView.clearHistory();
1323     }
1324
1325     // Override onBackPressed to handle the navigation drawer and mainWebView.
1326     @Override
1327     public void onBackPressed() {
1328         final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
1329
1330         // Close the navigation drawer if it is available.  GravityCompat.START is the drawer on the left on Left-to-Right layout text.
1331         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1332             drawerLayout.closeDrawer(GravityCompat.START);
1333         } else {
1334             // Load the previous URL if available.
1335             if (mainWebView.canGoBack()) {
1336                 mainWebView.goBack();
1337             } else {
1338                 // Pass onBackPressed to the system.
1339                 super.onBackPressed();
1340             }
1341         }
1342     }
1343
1344     @Override
1345     public void onPause() {
1346         // Pause `mainWebView`.
1347         mainWebView.onPause();
1348         mainWebView.pauseTimers();
1349
1350         // We need to pause the adView or it will continue to consume resources in the background on the free flavor.
1351         BannerAd.pauseAd(adView);
1352
1353         super.onPause();
1354     }
1355
1356     @Override
1357     public void onResume() {
1358         super.onResume();
1359
1360         // Resume `mainWebView`.
1361         mainWebView.resumeTimers();
1362         mainWebView.onResume();
1363
1364         // We need to resume the adView for the free flavor.
1365         BannerAd.resumeAd(adView);
1366     }
1367
1368     @Override
1369     public void onRestart() {
1370         super.onRestart();
1371
1372         // Apply the settings from shared preferences, which might have been changed in `Settings`.
1373         applySettings();
1374
1375         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1376         updatePrivacyIcons(true);
1377
1378     }
1379
1380     private void loadUrlFromTextBox() throws UnsupportedEncodingException {
1381         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
1382         String unformattedUrlString = urlTextBox.getText().toString().trim();
1383
1384         URL unformattedUrl = null;
1385         Uri.Builder formattedUri = new Uri.Builder();
1386
1387         // Check to see if unformattedUrlString is a valid URL.  Otherwise, convert it into a Duck Duck Go search.
1388         if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
1389             // Add http:// at the beginning if it is missing.  Otherwise the app will segfault.
1390             if (!unformattedUrlString.startsWith("http")) {
1391                 unformattedUrlString = "http://" + unformattedUrlString;
1392             }
1393
1394             // 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.
1395             try {
1396                 unformattedUrl = new URL(unformattedUrlString);
1397             } catch (MalformedURLException e) {
1398                 e.printStackTrace();
1399             }
1400
1401             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1402             final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1403             final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1404             final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1405             final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1406             final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1407
1408             formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1409             formattedUrlString = formattedUri.build().toString();
1410         } else {
1411             // Sanitize the search input and convert it to a DuckDuckGo search.
1412             final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1413
1414             // Use the correct search URL.
1415             if (javaScriptEnabled) {  // JavaScript is enabled.
1416                 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1417             } else { // JavaScript is disabled.
1418                 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1419             }
1420         }
1421
1422         mainWebView.loadUrl(formattedUrlString, customHeaders);
1423
1424         // Hide the keyboard so we can see the webpage.  `0` indicates no additional flags.
1425         inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1426     }
1427
1428     public void findPreviousOnPage(View view) {
1429         // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
1430         mainWebView.findNext(false);
1431     }
1432
1433     public void findNextOnPage(View view) {
1434         // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
1435         mainWebView.findNext(true);
1436     }
1437
1438     public void closeFindOnPage(View view) {
1439         // Delete the contents of `find_on_page_edittext`.
1440         findOnPageEditText.setText(null);
1441
1442         // Clear the highlighted phrases.
1443         mainWebView.clearMatches();
1444
1445         // Hide the Find on Page `RelativeLayout`.
1446         LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
1447         findOnPageLinearLayout.setVisibility(View.GONE);
1448
1449         // Show the URL app bar.
1450         Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar);
1451         appBarToolbar.setVisibility(View.VISIBLE);
1452
1453         // Hide the keyboard so we can see the webpage.  `0` indicates no additional flags.
1454         inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1455     }
1456
1457     private void applySettings() {
1458         // Get the shared preference values.  `this` references the current context.
1459         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1460
1461         // Store the values from `sharedPreferences` in variables.
1462         String userAgentString = sharedPreferences.getString("user_agent", "Default user agent");
1463         String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
1464         String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
1465         String javaScriptDisabledCustomSearchString = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
1466         String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
1467         String javaScriptEnabledCustomSearchString = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
1468         String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
1469         String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
1470         swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false);
1471         boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", true);
1472         boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
1473
1474         // Because they can be modified on-the-fly by the user, these default settings are only applied when the program first runs.
1475         if (javaScriptEnabled == null) {  // If `javaScriptEnabled` is null the program is just starting.
1476             // Get the values from `sharedPreferences`.
1477             javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
1478             firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
1479             thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
1480             domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
1481             saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
1482
1483             // Apply the default settings.
1484             mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
1485             cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1486             mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
1487             mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
1488             mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
1489
1490             // Set third-party cookies status if API >= 21.
1491             if (Build.VERSION.SDK_INT >= 21) {
1492                 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
1493             }
1494         }
1495
1496         // Apply the other settings from `sharedPreferences`.
1497         homepage = homepageString;
1498         swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
1499
1500         // Set the user agent initial status.
1501         switch (userAgentString) {
1502             case "Default user agent":
1503                 // Set the user agent to `""`, which uses the default value.
1504                 mainWebView.getSettings().setUserAgentString("");
1505                 break;
1506
1507             case "Custom user agent":
1508                 // Set the custom user agent.
1509                 mainWebView.getSettings().setUserAgentString(customUserAgentString);
1510                 break;
1511
1512             default:
1513                 // Use the selected user agent.
1514                 mainWebView.getSettings().setUserAgentString(userAgentString);
1515                 break;
1516         }
1517
1518         // Set JavaScript disabled search.
1519         if (javaScriptDisabledSearchString.equals("Custom URL")) {  // Get the custom URL string.
1520             javaScriptDisabledSearchURL = javaScriptDisabledCustomSearchString;
1521         } else {  // Use the string from the pre-built list.
1522             javaScriptDisabledSearchURL = javaScriptDisabledSearchString;
1523         }
1524
1525         // Set JavaScript enabled search.
1526         if (javaScriptEnabledSearchString.equals("Custom URL")) {  // Get the custom URL string.
1527             javaScriptEnabledSearchURL = javaScriptEnabledCustomSearchString;
1528         } else {  // Use the string from the pre-built list.
1529             javaScriptEnabledSearchURL = javaScriptEnabledSearchString;
1530         }
1531
1532         // Set Do Not Track status.
1533         if (doNotTrackEnabled) {
1534             customHeaders.put("DNT", "1");
1535         } else {
1536             customHeaders.remove("DNT");
1537         }
1538
1539         // Set Orbot proxy status.
1540         if (proxyThroughOrbot) {
1541             // Set the proxy.  `this` refers to the current activity where an `AlertDialog` might be displayed.
1542             OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
1543         } else {  // Reset the proxy to default.  The host is `""` and the port is `"0"`.
1544             OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
1545         }
1546     }
1547
1548     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
1549         // Get handles for the icons.
1550         MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript);
1551         MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies);
1552         MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage);
1553         MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData);
1554
1555         // Update `privacyIcon`.
1556         if (javaScriptEnabled) {  // JavaScript is enabled.
1557             privacyIcon.setIcon(R.drawable.javascript_enabled);
1558         } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled but cookies are enabled.
1559             privacyIcon.setIcon(R.drawable.warning);
1560         } else {  // All the dangerous features are disabled.
1561             privacyIcon.setIcon(R.drawable.privacy_mode);
1562         }
1563
1564         // Update `firstPartyCookiesIcon`.
1565         if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
1566             firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled);
1567         } else {  // First-party cookies are disabled.
1568             firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
1569         }
1570
1571         // Update `domStorageIcon`.
1572         if (javaScriptEnabled && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
1573             domStorageIcon.setIcon(R.drawable.dom_storage_enabled);
1574         } else if (javaScriptEnabled) {  // JavaScript is enabled but DOM storage is disabled.
1575             domStorageIcon.setIcon(R.drawable.dom_storage_disabled);
1576         } else {  // JavaScript is disabled, so DOM storage is ghosted.
1577             domStorageIcon.setIcon(R.drawable.dom_storage_ghosted);
1578         }
1579
1580         // Update `formDataIcon`.
1581         if (saveFormDataEnabled) {  // Form data is enabled.
1582             formDataIcon.setIcon(R.drawable.form_data_enabled);
1583         } else {  // Form data is disabled.
1584             formDataIcon.setIcon(R.drawable.form_data_disabled);
1585         }
1586
1587         // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.  `this` references the current activity.
1588         if (runInvalidateOptionsMenu) {
1589             ActivityCompat.invalidateOptionsMenu(this);
1590         }
1591     }
1592 }