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