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