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