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