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