Partial additional action button icons implementation.
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / MainWebViewActivity.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;
21
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.app.DialogFragment;
25 import android.app.DownloadManager;
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.SslError;
34 import android.os.Build;
35 import android.os.Bundle;
36 import android.preference.PreferenceManager;
37 import android.support.design.widget.NavigationView;
38 import android.support.design.widget.Snackbar;
39 import android.support.v4.app.ActivityCompat;
40 import android.support.v4.content.ContextCompat;
41 import android.support.v4.view.GravityCompat;
42 import android.support.v4.widget.DrawerLayout;
43 import android.support.v4.widget.SwipeRefreshLayout;
44 import android.support.v7.app.ActionBar;
45 import android.support.v7.app.ActionBarDrawerToggle;
46 import android.support.v7.app.AppCompatActivity;
47 import android.support.v7.widget.Toolbar;
48 import android.util.Patterns;
49 import android.view.KeyEvent;
50 import android.view.Menu;
51 import android.view.MenuItem;
52 import android.view.View;
53 import android.view.inputmethod.InputMethodManager;
54 import android.webkit.CookieManager;
55 import android.webkit.DownloadListener;
56 import android.webkit.SslErrorHandler;
57 import android.webkit.WebChromeClient;
58 import android.webkit.WebStorage;
59 import android.webkit.WebView;
60 import android.webkit.WebViewClient;
61 import android.webkit.WebViewDatabase;
62 import android.widget.EditText;
63 import android.widget.FrameLayout;
64 import android.widget.ImageView;
65 import android.widget.ProgressBar;
66
67 import java.io.UnsupportedEncodingException;
68 import java.net.MalformedURLException;
69 import java.net.URL;
70 import java.net.URLEncoder;
71 import java.util.HashMap;
72 import java.util.Map;
73
74 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
75 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener, SslCertificateError.SslCertificateErrorListener {
76     // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
77     // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
78     public static Bitmap favoriteIcon;
79
80     // `privacyBrowserActivity` is public static so it can be accessed from `SettingsFragment`.
81     // It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `onOptionsItemSelected()`,
82     public static Activity privacyBrowserActivity;
83
84     // `mainWebView` is public static so it can be accessed from `SettingsFragment`.
85     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, and `loadUrlFromTextBox()`.
86     public static WebView mainWebView;
87
88     // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`.
89     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
90     public static String formattedUrlString;
91
92     // `mainMenu` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onCreateOptionsMenu()` and `onOptionsItemSelected()`.
93     public static Menu mainMenu;
94
95     // `cookieManager` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
96     public static CookieManager cookieManager;
97
98     // `javaScriptEnabled` is public static so it can be accessed from `SettingsFragment`.
99     // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
100     public static boolean javaScriptEnabled;
101
102     // `firstPartyCookiesEnabled` is public static so it can be accessed from `SettingsFragment`.
103     // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, and `onOptionsItemSelected()`.
104     public static boolean firstPartyCookiesEnabled;
105
106     // `thridPartyCookiesEnables` is public static so it can be accessed from `SettingsFragment`.
107     // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, and `onOptionsItemSelected()`.
108     public static boolean thirdPartyCookiesEnabled;
109
110     // `domStorageEnabled` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `onOptionsItemSelected()`.
111     public static boolean domStorageEnabled;
112
113     // `saveFormDataEnabled` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onCreate()`, `onCreateOptionsMenu()`, and `onOptionsItemSelected()`.
114     public static boolean saveFormDataEnabled;
115
116     // `javaScriptDisabledSearchURL` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onCreate()` and `loadURLFromTextBox()`.
117     public static String javaScriptDisabledSearchURL;
118
119     // `javaScriptEnabledSearchURL` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onCreate()` and `loadURLFromTextBox()`.
120     public static String javaScriptEnabledSearchURL;
121
122     // `homepage` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onCreate()` and `onOptionsItemSelected()`.
123     public static String homepage;
124
125     // `swipeToRefresh` is public static so it can be accessed from SettingsFragment.  It is also used in onCreate().
126     public static SwipeRefreshLayout swipeToRefresh;
127
128     // `swipeToRefreshEnabled` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onCreate()`.
129     public static boolean swipeToRefreshEnabled;
130
131     // `customHeader` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()` and `loadUrlFromTextBox()`.
132     public static Map<String, String> customHeaders = new HashMap<String, String>();
133
134
135
136     // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
137     private ActionBarDrawerToggle drawerToggle;
138
139     // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
140     private DrawerLayout drawerLayout;
141
142     // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
143     private EditText urlTextBox;
144
145     // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
146     private View adView;
147
148     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
149     private SslErrorHandler sslErrorHandler;
150
151     @Override
152     // 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.
153     @SuppressLint("SetJavaScriptEnabled")
154     protected void onCreate(Bundle savedInstanceState) {
155         super.onCreate(savedInstanceState);
156         setContentView(R.layout.main_coordinatorlayout);
157
158         // We need a handle for the activity, which is accessed from `SettingsFragment` and fed into `updatePrivacyIcons()`.
159         privacyBrowserActivity = this;
160
161         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
162         Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
163         setSupportActionBar(supportAppBar);
164         final ActionBar appBar = getSupportActionBar();
165
166         // This is needed to get rid of the Android Studio warning that appBar might be null.
167         assert appBar != null;
168
169         // Add the custom url_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
170         appBar.setCustomView(R.layout.url_bar);
171         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
172
173         // Set the "go" button on the keyboard to load the URL in urlTextBox.
174         urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
175         urlTextBox.setOnKeyListener(new View.OnKeyListener() {
176             public boolean onKey(View v, int keyCode, KeyEvent event) {
177                 // If the event is a key-down event on the "enter" button, load the URL.
178                 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
179                     // Load the URL into the mainWebView and consume the event.
180                     try {
181                         loadUrlFromTextBox();
182                     } catch (UnsupportedEncodingException e) {
183                         e.printStackTrace();
184                     }
185                     // If the enter key was pressed, consume the event.
186                     return true;
187                 } else {
188                     // If any other key was pressed, do not consume the event.
189                     return false;
190                 }
191             }
192         });
193
194         final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
195
196         // Implement swipe to refresh
197         swipeToRefresh = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
198         assert swipeToRefresh != null; //This assert removes the incorrect warning on the following line that swipeToRefresh might be null.
199         swipeToRefresh.setColorSchemeResources(R.color.blue);
200         swipeToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
201             @Override
202             public void onRefresh() {
203                 mainWebView.reload();
204             }
205         });
206
207         mainWebView = (WebView) findViewById(R.id.mainWebView);
208
209         // Create the navigation drawer.
210         drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
211         // The DrawerTitle identifies the drawer in accessibility mode.
212         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
213
214         // Listen for touches on the navigation menu.
215         final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
216         assert navigationView != null; // This assert removes the incorrect warning on the following line that navigationView might be null.
217         navigationView.setNavigationItemSelectedListener(this);
218
219         // drawerToggle creates the hamburger icon at the start of the AppBar.
220         drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
221
222         // 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).
223         customHeaders.put("X-Requested-With", "");
224
225         mainWebView.setWebViewClient(new WebViewClient() {
226             // shouldOverrideUrlLoading makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
227             @Override
228             public boolean shouldOverrideUrlLoading(WebView view, String url) {
229                 // Use an external email program if the link begins with "mailto:".
230                 if (url.startsWith("mailto:")) {
231                     // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
232                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
233
234                     // Parse the url and set it as the data for the `Intent`.
235                     emailIntent.setData(Uri.parse(url));
236
237                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
238                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
239
240                     // Make it so.
241                     startActivity(emailIntent);
242                     return true;
243                 } else {  // Load the URL in Privacy Browser.
244                     mainWebView.loadUrl(url, customHeaders);
245                     return true;
246                 }
247             }
248
249             // Update the URL in urlTextBox when the page starts to load.
250             @Override
251             public void onPageStarted(WebView view, String url, Bitmap favicon) {
252                 urlTextBox.setText(url);
253             }
254
255             // Update formattedUrlString and urlTextBox.  It is necessary to do this after the page finishes loading because the final URL can change during load.
256             @Override
257             public void onPageFinished(WebView view, String url) {
258                 formattedUrlString = url;
259
260                 // Only update urlTextBox if the user is not typing in it.
261                 if (!urlTextBox.hasFocus()) {
262                     urlTextBox.setText(formattedUrlString);
263                 }
264             }
265
266             // Handle SSL Certificate errors.
267             @Override
268             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
269                 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
270                 sslErrorHandler = handler;
271
272                 // Display the SSL error `AlertDialog`.
273                 DialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error);
274                 sslCertificateErrorDialogFragment.show(getFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
275             }
276         });
277
278         mainWebView.setWebChromeClient(new WebChromeClient() {
279             // Update the progress bar when a page is loading.
280             @Override
281             public void onProgressChanged(WebView view, int progress) {
282                 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
283                 progressBar.setProgress(progress);
284                 if (progress < 100) {
285                     progressBar.setVisibility(View.VISIBLE);
286                 } else {
287                     progressBar.setVisibility(View.GONE);
288
289                     //Stop the SwipeToRefresh indicator if it is running
290                     swipeToRefresh.setRefreshing(false);
291                 }
292             }
293
294             // Set the favorite icon when it changes.
295             @Override
296             public void onReceivedIcon(WebView view, Bitmap icon) {
297                 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
298                 favoriteIcon = icon;
299
300                 // Place the favorite icon in the appBar.
301                 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
302                 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
303             }
304
305             // Enter full screen video
306             @Override
307             public void onShowCustomView(View view, CustomViewCallback callback) {
308                 appBar.hide();
309
310                 // Show the fullScreenVideoFrameLayout.
311                 assert fullScreenVideoFrameLayout != null; //This assert removes the incorrect warning on the following line that fullScreenVideoFrameLayout might be null.
312                 fullScreenVideoFrameLayout.addView(view);
313                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
314
315                 // Hide the mainWebView.
316                 mainWebView.setVisibility(View.GONE);
317
318                 // Hide the ad if this is the free flavor.
319                 BannerAd.hideAd(adView);
320
321                 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
322                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
323                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
324                  */
325                 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
326             }
327
328             // Exit full screen video
329             public void onHideCustomView() {
330                 appBar.show();
331
332                 // Show the mainWebView.
333                 mainWebView.setVisibility(View.VISIBLE);
334
335                 // Show the ad if this is the free flavor.
336                 BannerAd.showAd(adView);
337
338                 // Hide the fullScreenVideoFrameLayout.
339                 assert fullScreenVideoFrameLayout != null; //This assert removes the incorrect warning on the following line that fullScreenVideoFrameLayout might be null.
340                 fullScreenVideoFrameLayout.removeAllViews();
341                 fullScreenVideoFrameLayout.setVisibility(View.GONE);
342             }
343         });
344
345         // Allow the downloading of files.
346         mainWebView.setDownloadListener(new DownloadListener() {
347             // Launch the Android download manager when a link leads to a download.
348             @Override
349             public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
350                 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
351                 DownloadManager.Request requestUri = new DownloadManager.Request(Uri.parse(url));
352
353                 // Add the URL as the description for the download.
354                 requestUri.setDescription(url);
355
356                 // Show the download notification after the download is completed.
357                 requestUri.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
358
359                 // Initiate the download and display a Snackbar.
360                 downloadManager.enqueue(requestUri);
361                 Snackbar.make(findViewById(R.id.mainWebView), R.string.download_started, Snackbar.LENGTH_SHORT).show();
362             }
363         });
364
365         // Allow pinch to zoom.
366         mainWebView.getSettings().setBuiltInZoomControls(true);
367
368         // Hide zoom controls.
369         mainWebView.getSettings().setDisplayZoomControls(false);
370
371
372         // Initialize the default preference values the first time the program is run.
373         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
374
375         // Get the shared preference values.
376         SharedPreferences savedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
377
378         // Set JavaScript initial status.  The default value is false.
379         javaScriptEnabled = savedPreferences.getBoolean("javascript_enabled", false);
380         mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
381
382         // Initialize cookieManager.
383         cookieManager = CookieManager.getInstance();
384
385         // Set cookies initial status.  The default value is false.
386         firstPartyCookiesEnabled = savedPreferences.getBoolean("first_party_cookies_enabled", false);
387         cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
388
389         // Set third-party cookies initial status if API >= 21.  The default value is false.
390         if (Build.VERSION.SDK_INT >= 21) {
391             thirdPartyCookiesEnabled = savedPreferences.getBoolean("third_party_cookies_enabled", false);
392             cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
393         }
394
395         // Set DOM storage initial status.  The default value is false.
396         domStorageEnabled = savedPreferences.getBoolean("dom_storage_enabled", false);
397         mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
398
399         // Set the saved form data initial status.  The default is false.
400         saveFormDataEnabled = savedPreferences.getBoolean("save_form_data_enabled", false);
401         mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
402
403         // Set the user agent initial status.
404         String userAgentString = savedPreferences.getString("user_agent", "Default user agent");
405         switch (userAgentString) {
406             case "Default user agent":
407                 // Do nothing.
408                 break;
409
410             case "Custom user agent":
411                 // Set the custom user agent on mainWebView,  The default is "PrivacyBrowser/1.0".
412                 mainWebView.getSettings().setUserAgentString(savedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
413                 break;
414
415             default:
416                 // Set the selected user agent on mainWebView.  The default is "PrivacyBrowser/1.0".
417                 mainWebView.getSettings().setUserAgentString(savedPreferences.getString("user_agent", "PrivacyBrowser/1.0"));
418                 break;
419         }
420
421         // Set the initial string for JavaScript disabled search.
422         if (savedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=").equals("Custom URL")) {
423             // Get the custom URL string.  The default is "".
424             javaScriptDisabledSearchURL = savedPreferences.getString("javascript_disabled_search_custom_url", "");
425         } else {
426             // Use the string from javascript_disabled_search.
427             javaScriptDisabledSearchURL = savedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
428         }
429
430         // Set the initial string for JavaScript enabled search.
431         if (savedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=").equals("Custom URL")) {
432             // Get the custom URL string.  The default is "".
433             javaScriptEnabledSearchURL = savedPreferences.getString("javascript_enabled_search_custom_url", "");
434         } else {
435             // Use the string from javascript_enabled_search.
436             javaScriptEnabledSearchURL = savedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
437         }
438
439
440         // Set the homepage initial status.  The default value is `https://www.duckduckgo.com`.
441         homepage = savedPreferences.getString("homepage", "https://www.duckduckgo.com");
442
443         // Set the font size initial status.  the default value is `100`.
444         String defaultFontSizeString = savedPreferences.getString("default_font_size", "100");
445         mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
446
447         // Set the swipe to refresh initial status.  The default is `true`.
448         swipeToRefreshEnabled = savedPreferences.getBoolean("swipe_to_refresh_enabled", true);
449         swipeToRefresh.setEnabled(swipeToRefreshEnabled);
450
451
452         // Get the intent information that started the app.
453         final Intent intent = getIntent();
454
455         if (intent.getData() != null) {
456             // Get the intent data and convert it to a string.
457             final Uri intentUriData = intent.getData();
458             formattedUrlString = intentUriData.toString();
459         }
460
461         // If formattedUrlString is null assign the homepage to it.
462         if (formattedUrlString == null) {
463             formattedUrlString = homepage;
464         }
465
466         // Load the initial website.
467         mainWebView.loadUrl(formattedUrlString, customHeaders);
468
469         // If the favorite icon is null, load the default.
470         if (favoriteIcon == null) {
471             // We have to use `ContextCompat` until API >= 21.
472             Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
473             BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
474             favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
475         }
476
477         // Initialize AdView for the free flavor and request an ad.  If this is not the free flavor BannerAd.requestAd() does nothing.
478         adView = findViewById(R.id.adView);
479         BannerAd.requestAd(adView);
480     }
481
482
483     @Override
484     protected void onNewIntent(Intent intent) {
485         // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
486         setIntent(intent);
487
488         if (intent.getData() != null) {
489             // Get the intent data and convert it to a string.
490             final Uri intentUriData = intent.getData();
491             formattedUrlString = intentUriData.toString();
492         }
493
494         // Close the navigation drawer if it is open.
495         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
496             drawerLayout.closeDrawer(GravityCompat.START);
497         }
498
499         // Load the website.
500         mainWebView.loadUrl(formattedUrlString, customHeaders);
501
502         // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
503         mainWebView.requestFocus();
504     }
505
506     @Override
507     public boolean onCreateOptionsMenu(Menu menu) {
508         // Inflate the menu; this adds items to the action bar if it is present.
509         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
510
511         // Set mainMenu so it can be used by onOptionsItemSelected.
512         mainMenu = menu;
513
514         // Set the initial status of the privacy icon.
515         updatePrivacyIcons(privacyBrowserActivity);
516
517         // Get MenuItems for checkable menu items.
518         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
519         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
520         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
521         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
522
523         // Set the initial status of the menu item checkboxes.
524         toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
525         toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
526         toggleDomStorage.setChecked(domStorageEnabled);
527         toggleSaveFormData.setChecked(saveFormDataEnabled);
528
529         return true;
530     }
531
532     @Override
533     public boolean onPrepareOptionsMenu(Menu menu) {
534         // Only enable Third-Party Cookies if SDK >= 21 and First-Party Cookies are enabled.
535         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
536         if ((Build.VERSION.SDK_INT >= 21) && firstPartyCookiesEnabled) {
537             toggleThirdPartyCookies.setEnabled(true);
538         } else {
539             toggleThirdPartyCookies.setEnabled(false);
540         }
541
542         // Enable DOM Storage if JavaScript is enabled.
543         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
544         toggleDomStorage.setEnabled(javaScriptEnabled);
545
546         // Enable Clear Cookies if there are any.
547         MenuItem clearCookies = menu.findItem(R.id.clearCookies);
548         clearCookies.setEnabled(cookieManager.hasCookies());
549
550         // Enable Clear Form Data is there is any.
551         MenuItem clearFormData = menu.findItem(R.id.clearFormData);
552         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
553         clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
554
555         // Initialize font size variables.
556         int fontSize = mainWebView.getSettings().getTextZoom();
557         String fontSizeTitle;
558         MenuItem selectedFontSizeMenuItem;
559
560         // Prepare the font size title and current size menu item.
561         switch (fontSize) {
562             case 50:
563                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
564                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
565                 break;
566
567             case 75:
568                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
569                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
570                 break;
571
572             case 100:
573                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
574                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
575                 break;
576
577             case 125:
578                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
579                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
580                 break;
581
582             case 150:
583                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
584                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
585                 break;
586
587             case 175:
588                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
589                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
590                 break;
591
592             case 200:
593                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
594                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
595                 break;
596
597             default:
598                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
599                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
600                 break;
601         }
602
603         // Set the font size title and select the current size menu item.
604         MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
605         fontSizeMenuItem.setTitle(fontSizeTitle);
606         selectedFontSizeMenuItem.setChecked(true);
607
608         // Only show `Refresh` if `swipeToRefresh` is disabled.
609         MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
610         refreshMenuItem.setVisible(!swipeToRefreshEnabled);
611
612         // Run all the other default commands.
613         super.onPrepareOptionsMenu(menu);
614
615         // `return true` displays the menu.
616         return true;
617     }
618
619     @Override
620     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
621     @SuppressLint("SetJavaScriptEnabled")
622     // removeAllCookies is deprecated, but it is required for API < 21.
623     @SuppressWarnings("deprecation")
624     public boolean onOptionsItemSelected(MenuItem menuItem) {
625         int menuItemId = menuItem.getItemId();
626
627         // Set the commands that relate to the menu entries.
628         switch (menuItemId) {
629             case R.id.toggleJavaScript:
630                 // Switch the status of javaScriptEnabled.
631                 javaScriptEnabled = !javaScriptEnabled;
632
633                 // Apply the new JavaScript status.
634                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
635
636                 // Update the privacy icon.
637                 updatePrivacyIcons(privacyBrowserActivity);
638
639                 // Display a `Snackbar`.
640                 if (javaScriptEnabled) {
641                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
642                 } else {
643                     if (firstPartyCookiesEnabled) {
644                         Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
645                     } else {
646                         Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
647                     }
648                 }
649
650                 // Reload the WebView.
651                 mainWebView.reload();
652                 return true;
653
654             case R.id.toggleFirstPartyCookies:
655                 // Switch the status of firstPartyCookiesEnabled.
656                 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
657
658                 // Update the menu checkbox.
659                 menuItem.setChecked(firstPartyCookiesEnabled);
660
661                 // Apply the new cookie status.
662                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
663
664                 // Update the privacy icon.
665                 updatePrivacyIcons(privacyBrowserActivity);
666
667                 // Display a `Snackbar`.
668                 if (firstPartyCookiesEnabled) {
669                     Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
670                 } else {
671                     if (javaScriptEnabled) {
672                         Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
673                     } else {
674                         Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
675                     }
676                 }
677
678                 // Reload the WebView.
679                 mainWebView.reload();
680                 return true;
681
682             case R.id.toggleThirdPartyCookies:
683                 if (Build.VERSION.SDK_INT >= 21) {
684                     // Switch the status of thirdPartyCookiesEnabled.
685                     thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
686
687                     // Update the menu checkbox.
688                     menuItem.setChecked(thirdPartyCookiesEnabled);
689
690                     // Apply the new cookie status.
691                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
692
693                     // Display a `Snackbar`.
694                     if (thirdPartyCookiesEnabled) {
695                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
696                     } else {
697                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
698                     }
699
700                     // Reload the WebView.
701                     mainWebView.reload();
702                 } // Else do nothing because SDK < 21.
703                 return true;
704
705             case R.id.toggleDomStorage:
706                 // Switch the status of domStorageEnabled.
707                 domStorageEnabled = !domStorageEnabled;
708
709                 // Update the menu checkbox.
710                 menuItem.setChecked(domStorageEnabled);
711
712                 // Apply the new DOM Storage status.
713                 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
714
715                 // Display a `Snackbar`.
716                 if (domStorageEnabled) {
717                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
718                 } else {
719                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
720                 }
721
722                 // Reload the WebView.
723                 mainWebView.reload();
724                 return true;
725
726             case R.id.toggleSaveFormData:
727                 // Switch the status of saveFormDataEnabled.
728                 saveFormDataEnabled = !saveFormDataEnabled;
729
730                 // Update the menu checkbox.
731                 menuItem.setChecked(saveFormDataEnabled);
732
733                 // Apply the new form data status.
734                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
735
736                 // Display a `Snackbar`.
737                 if (saveFormDataEnabled) {
738                     Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
739                 } else {
740                     Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
741                 }
742
743                 // Reload the WebView.
744                 mainWebView.reload();
745                 return true;
746
747             case R.id.clearCookies:
748                 if (Build.VERSION.SDK_INT < 21) {
749                     cookieManager.removeAllCookie();
750                 } else {
751                     cookieManager.removeAllCookies(null);
752                 }
753                 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
754                 return true;
755
756             case R.id.clearDomStorage:
757                 WebStorage webStorage = WebStorage.getInstance();
758                 webStorage.deleteAllData();
759                 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
760                 return true;
761
762             case R.id.clearFormData:
763                 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
764                 mainWebViewDatabase.clearFormData();
765                 mainWebView.reload();
766                 return true;
767
768             case R.id.fontSizeFiftyPercent:
769                 mainWebView.getSettings().setTextZoom(50);
770                 return true;
771
772             case R.id.fontSizeSeventyFivePercent:
773                 mainWebView.getSettings().setTextZoom(75);
774                 return true;
775
776             case R.id.fontSizeOneHundredPercent:
777                 mainWebView.getSettings().setTextZoom(100);
778                 return true;
779
780             case R.id.fontSizeOneHundredTwentyFivePercent:
781                 mainWebView.getSettings().setTextZoom(125);
782                 return true;
783
784             case R.id.fontSizeOneHundredFiftyPercent:
785                 mainWebView.getSettings().setTextZoom(150);
786                 return true;
787
788             case R.id.fontSizeOneHundredSeventyFivePercent:
789                 mainWebView.getSettings().setTextZoom(175);
790                 return true;
791
792             case R.id.fontSizeTwoHundredPercent:
793                 mainWebView.getSettings().setTextZoom(200);
794                 return true;
795
796             case R.id.share:
797                 Intent shareIntent = new Intent();
798                 shareIntent.setAction(Intent.ACTION_SEND);
799                 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
800                 shareIntent.setType("text/plain");
801                 startActivity(Intent.createChooser(shareIntent, "Share URL"));
802                 return true;
803
804             case R.id.addToHomescreen:
805                 // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `@string/create_shortcut`.
806                 DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
807                 createHomeScreenShortcutDialogFragment.show(getFragmentManager(), getResources().getString(R.string.create_shortcut));
808
809                 //Everything else will be handled by CreateHomeScreenShortcut and the associated listeners below.
810                 return true;
811
812             case R.id.refresh:
813                 mainWebView.reload();
814                 return true;
815
816             default:
817                 // Don't consume the event.
818                 return super.onOptionsItemSelected(menuItem);
819         }
820     }
821
822     @Override
823     // removeAllCookies is deprecated, but it is required for API < 21.
824     @SuppressWarnings("deprecation")
825     public boolean onNavigationItemSelected(MenuItem menuItem) {
826         int menuItemId = menuItem.getItemId();
827
828         switch (menuItemId) {
829             case R.id.home:
830                 mainWebView.loadUrl(homepage, customHeaders);
831                 break;
832
833             case R.id.back:
834                 if (mainWebView.canGoBack()) {
835                     mainWebView.goBack();
836                 }
837                 break;
838
839             case R.id.forward:
840                 if (mainWebView.canGoForward()) {
841                     mainWebView.goForward();
842                 }
843                 break;
844
845             case R.id.bookmarks:
846                 // Launch BookmarksActivity.
847                 Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
848                 startActivity(bookmarksIntent);
849                 break;
850
851             case R.id.downloads:
852                 // Launch the system Download Manager.
853                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
854
855                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
856                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
857
858                 startActivity(downloadManagerIntent);
859                 break;
860
861             case R.id.settings:
862                 // Launch `SettingsActivity`.
863                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
864                 startActivity(settingsIntent);
865                 break;
866
867             case R.id.guide:
868                 // Launch `GuideActivity`.
869                 Intent guideIntent = new Intent(this, GuideActivity.class);
870                 startActivity(guideIntent);
871                 break;
872
873             case R.id.about:
874                 // Launch `AboutActivity`.
875                 Intent aboutIntent = new Intent(this, AboutActivity.class);
876                 startActivity(aboutIntent);
877                 break;
878
879             case R.id.clearAndExit:
880                 // Clear cookies.  The commands changed slightly in API 21.
881                 if (Build.VERSION.SDK_INT >= 21) {
882                     cookieManager.removeAllCookies(null);
883                 } else {
884                     cookieManager.removeAllCookie();
885                 }
886
887                 // Clear DOM storage.
888                 WebStorage domStorage = WebStorage.getInstance();
889                 domStorage.deleteAllData();
890
891                 // Clear form data.
892                 WebViewDatabase formData = WebViewDatabase.getInstance(this);
893                 formData.clearFormData();
894
895                 // Clear cache.  The argument of "true" includes disk files.
896                 mainWebView.clearCache(true);
897
898                 // Clear the back/forward history.
899                 mainWebView.clearHistory();
900
901                 // Clear any SSL certificate preferences.
902                 MainWebViewActivity.mainWebView.clearSslPreferences();
903
904                 // Clear `formattedUrlString`.
905                 formattedUrlString = null;
906
907                 // Destroy the internal state of the webview.
908                 mainWebView.destroy();
909
910                 // Close Privacy Browser.  finishAndRemoveTask also removes Privacy Browser from the recent app list.
911                 if (Build.VERSION.SDK_INT >= 21) {
912                     finishAndRemoveTask();
913                 } else {
914                     finish();
915                 }
916                 break;
917
918             default:
919                 break;
920         }
921
922         // Close the navigation drawer.
923         drawerLayout.closeDrawer(GravityCompat.START);
924         return true;
925     }
926
927     @Override
928     public void onPostCreate(Bundle savedInstanceState) {
929         super.onPostCreate(savedInstanceState);
930
931         // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
932         drawerToggle.syncState();
933     }
934
935     @Override
936     public void onConfigurationChanged(Configuration newConfig) {
937         super.onConfigurationChanged(newConfig);
938
939         // Reload the ad if this is the free flavor.
940         BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
941
942         // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor by BannerAd.reloadAfterRotate().
943         adView = findViewById(R.id.adView);
944
945         // `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
946         invalidateOptionsMenu();
947     }
948
949     @Override
950     public void onCancelCreateHomeScreenShortcut(DialogFragment dialogFragment) {
951         // Do nothing because the user selected "Cancel".
952     }
953
954     @Override
955     public void onCreateHomeScreenShortcut(DialogFragment dialogFragment) {
956         // Get shortcutNameEditText from the alert dialog.
957         EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
958
959         // Create the bookmark shortcut based on formattedUrlString.
960         Intent bookmarkShortcut = new Intent();
961         bookmarkShortcut.setAction(Intent.ACTION_VIEW);
962         bookmarkShortcut.setData(Uri.parse(formattedUrlString));
963
964         // Place the bookmark shortcut on the home screen.
965         Intent placeBookmarkShortcut = new Intent();
966         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
967         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
968         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
969         placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
970         sendBroadcast(placeBookmarkShortcut);
971     }
972
973     public void viewSslCertificate(View view) {
974         // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
975         DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate();
976         viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
977     }
978
979     @Override
980     public void onSslErrorCancel() {
981         sslErrorHandler.cancel();
982     }
983
984     @Override
985     public void onSslErrorProceed() {
986         sslErrorHandler.proceed();
987     }
988
989     // Override onBackPressed to handle the navigation drawer and mainWebView.
990     @Override
991     public void onBackPressed() {
992         final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
993
994         // Close the navigation drawer if it is available.  GravityCompat.START is the drawer on the left on Left-to-Right layout text.
995         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
996             drawerLayout.closeDrawer(GravityCompat.START);
997         } else {
998             // Load the previous URL if available.
999             assert mainWebView != null; //This assert removes the incorrect warning in Android Studio on the following line that mainWebView might be null.
1000             if (mainWebView.canGoBack()) {
1001                 mainWebView.goBack();
1002             } else {
1003                 // Pass onBackPressed to the system.
1004                 super.onBackPressed();
1005             }
1006         }
1007     }
1008
1009     @Override
1010     public void onPause() {
1011         // We need to pause the adView or it will continue to consume resources in the background on the free flavor.
1012         BannerAd.pauseAd(adView);
1013
1014         super.onPause();
1015     }
1016
1017     @Override
1018     public void onResume() {
1019         super.onResume();
1020
1021         // We need to resume the adView for the free flavor.
1022         BannerAd.resumeAd(adView);
1023     }
1024
1025     private void loadUrlFromTextBox() throws UnsupportedEncodingException {
1026         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
1027         String unformattedUrlString = urlTextBox.getText().toString().trim();
1028
1029         URL unformattedUrl = null;
1030         Uri.Builder formattedUri = new Uri.Builder();
1031
1032         // Check to see if unformattedUrlString is a valid URL.  Otherwise, convert it into a Duck Duck Go search.
1033         if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
1034             // Add http:// at the beginning if it is missing.  Otherwise the app will segfault.
1035             if (!unformattedUrlString.startsWith("http")) {
1036                 unformattedUrlString = "http://" + unformattedUrlString;
1037             }
1038
1039             // 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.
1040             try {
1041                 unformattedUrl = new URL(unformattedUrlString);
1042             } catch (MalformedURLException e) {
1043                 e.printStackTrace();
1044             }
1045
1046             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1047             final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1048             final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1049             final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1050             final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1051             final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1052
1053             formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1054             formattedUrlString = formattedUri.build().toString();
1055         } else {
1056             // Sanitize the search input and convert it to a DuckDuckGo search.
1057             final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1058
1059             // Use the correct search URL based on javaScriptEnabled.
1060             if (javaScriptEnabled) {
1061                 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1062             } else { // JavaScript is disabled.
1063                 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1064             }
1065         }
1066
1067         mainWebView.loadUrl(formattedUrlString, customHeaders);
1068
1069         // Hides the keyboard so we can see the webpage.
1070         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
1071         inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1072     }
1073
1074     public static void updatePrivacyIcons(Activity activity) {
1075         // Get handles for the icons.
1076         MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript);
1077         MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies);
1078         MenuItem thirdPartyCookiesIcon = mainMenu.findItem(R.id.toggleThirdPartyCookies);
1079         MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage);
1080         MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData);
1081
1082         // Update `privacyIcon`.
1083         if (javaScriptEnabled) {
1084             // `JavaScript` is enabled.
1085             privacyIcon.setIcon(R.drawable.javascript_enabled);
1086         } else {
1087             if (firstPartyCookiesEnabled) {
1088                 // `JavaScript` is disabled but cookies are enabled.
1089                 privacyIcon.setIcon(R.drawable.warning);
1090             } else {
1091                 // All the dangerous features are disabled.
1092                 privacyIcon.setIcon(R.drawable.privacy_mode);
1093             }
1094         }
1095
1096         // Update `firstPartyCookiesIcon`.
1097         if (firstPartyCookiesEnabled) {
1098             // First-party cookies are enabled.
1099             firstPartyCookiesIcon.setIcon(R.drawable.cookies_warning);
1100         } else {
1101             // First-party cookies are disabled.
1102             firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
1103         }
1104
1105         // Update `thirdPartyCookiesIcon`.
1106         if (firstPartyCookiesEnabled) {
1107             if (thirdPartyCookiesEnabled) {
1108                 //  Third-party cookies are enabled.  Bad!
1109                 thirdPartyCookiesIcon.setIcon(R.drawable.cookies_critical);
1110             } else {
1111                 // Third-party cookies are disabled.
1112                 thirdPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
1113             }
1114         } else {
1115             // First-party cookies are disabled, so third-party cookies are ghosted.
1116             thirdPartyCookiesIcon.setIcon(R.drawable.cookies_ghosted);
1117         }
1118
1119         // Update `domStorageIcon`.
1120         if (javaScriptEnabled) {
1121             if (domStorageEnabled) {
1122                 domStorageIcon.setIcon(R.drawable.dom_storage_enabled);
1123             } else {
1124                 domStorageIcon.setIcon(R.drawable.dom_storage_disabled);
1125             }
1126         } else {
1127             domStorageIcon.setIcon(R.drawable.dom_storage_ghosted);
1128         }
1129
1130         // Update `formDataIcon`.
1131         if (saveFormDataEnabled) {
1132             formDataIcon.setIcon(R.drawable.form_data_enabled);
1133         } else {
1134             formDataIcon.setIcon(R.drawable.form_data_disabled);
1135         }
1136
1137         // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
1138         ActivityCompat.invalidateOptionsMenu(activity);
1139     }
1140 }