Add a logcat activity. https://redmine.stoutner.com/issues/264
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
1 /*
2  * Copyright © 2015-2019 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.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.DialogFragment;
28 import android.app.DownloadManager;
29 import android.app.SearchManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ClipData;
33 import android.content.ClipboardManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences;
38 import android.content.pm.PackageManager;
39 import android.content.res.Configuration;
40 import android.content.res.Resources;
41 import android.database.Cursor;
42 import android.graphics.Bitmap;
43 import android.graphics.BitmapFactory;
44 import android.graphics.Typeface;
45 import android.graphics.drawable.BitmapDrawable;
46 import android.graphics.drawable.Drawable;
47 import android.net.Uri;
48 import android.net.http.SslCertificate;
49 import android.net.http.SslError;
50 import android.os.AsyncTask;
51 import android.os.Build;
52 import android.os.Bundle;
53 import android.os.Environment;
54 import android.os.Handler;
55 import android.preference.PreferenceManager;
56 import android.print.PrintDocumentAdapter;
57 import android.print.PrintManager;
58 import android.support.annotation.NonNull;
59 import android.support.design.widget.CoordinatorLayout;
60 import android.support.design.widget.FloatingActionButton;
61 import android.support.design.widget.NavigationView;
62 import android.support.design.widget.Snackbar;
63 import android.support.v4.app.ActivityCompat;
64 import android.support.v4.app.FragmentManager;
65 import android.support.v4.content.ContextCompat;
66 // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
67 import android.support.v4.content.pm.ShortcutInfoCompat;
68 import android.support.v4.content.pm.ShortcutManagerCompat;
69 import android.support.v4.graphics.drawable.IconCompat;
70 import android.support.v4.view.GravityCompat;
71 import android.support.v4.widget.DrawerLayout;
72 import android.support.v4.widget.SwipeRefreshLayout;
73 import android.support.v7.app.ActionBar;
74 import android.support.v7.app.ActionBarDrawerToggle;
75 import android.support.v7.app.AppCompatActivity;
76 import android.support.v7.app.AppCompatDialogFragment;
77 import android.support.v7.widget.Toolbar;
78 import android.text.Editable;
79 import android.text.Spanned;
80 import android.text.TextWatcher;
81 import android.text.style.ForegroundColorSpan;
82 import android.util.Patterns;
83 import android.view.ContextMenu;
84 import android.view.GestureDetector;
85 import android.view.KeyEvent;
86 import android.view.Menu;
87 import android.view.MenuItem;
88 import android.view.MotionEvent;
89 import android.view.View;
90 import android.view.ViewGroup;
91 import android.view.WindowManager;
92 import android.view.inputmethod.InputMethodManager;
93 import android.webkit.CookieManager;
94 import android.webkit.HttpAuthHandler;
95 import android.webkit.SslErrorHandler;
96 import android.webkit.ValueCallback;
97 import android.webkit.WebBackForwardList;
98 import android.webkit.WebChromeClient;
99 import android.webkit.WebResourceResponse;
100 import android.webkit.WebSettings;
101 import android.webkit.WebStorage;
102 import android.webkit.WebView;
103 import android.webkit.WebViewClient;
104 import android.webkit.WebViewDatabase;
105 import android.widget.ArrayAdapter;
106 import android.widget.CursorAdapter;
107 import android.widget.EditText;
108 import android.widget.FrameLayout;
109 import android.widget.ImageView;
110 import android.widget.LinearLayout;
111 import android.widget.ListView;
112 import android.widget.ProgressBar;
113 import android.widget.RadioButton;
114 import android.widget.RelativeLayout;
115 import android.widget.TextView;
116
117 import com.stoutner.privacybrowser.BuildConfig;
118 import com.stoutner.privacybrowser.R;
119 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
120 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
121 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
122 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
123 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
124 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
125 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
126 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
127 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
128 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
129 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
130 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
131 import com.stoutner.privacybrowser.helpers.AdHelper;
132 import com.stoutner.privacybrowser.helpers.BlockListHelper;
133 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
134 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
135 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
136 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
137 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
138
139 import java.io.ByteArrayInputStream;
140 import java.io.ByteArrayOutputStream;
141 import java.io.File;
142 import java.io.IOException;
143 import java.io.UnsupportedEncodingException;
144 import java.lang.ref.WeakReference;
145 import java.net.InetAddress;
146 import java.net.MalformedURLException;
147 import java.net.URL;
148 import java.net.URLDecoder;
149 import java.net.URLEncoder;
150 import java.net.UnknownHostException;
151 import java.util.ArrayList;
152 import java.util.Date;
153 import java.util.HashMap;
154 import java.util.HashSet;
155 import java.util.List;
156 import java.util.Map;
157 import java.util.Set;
158
159 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
160 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
161         CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
162         DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
163         HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener,
164         SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
165
166     // `darkTheme` is public static so it can be accessed from everywhere.
167     public static boolean darkTheme;
168
169     // `allowScreenshots` is public static so it can be accessed from everywhere.  It is also used in `onCreate()`.
170     public static boolean allowScreenshots;
171
172     // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
173     // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`.  It is also used in `onCreate()`,
174     // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
175     public static Bitmap favoriteIconBitmap;
176
177     // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`.  It is also used in `onCreate()` and `applyDomainSettings`.
178     public static Bitmap favoriteIconDefaultBitmap;
179
180     // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`, and `PinnedMismatchDialog`.
181     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
182     public static String formattedUrlString;
183
184     // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedMismatchDialog`, and `ViewSslCertificateDialog`.
185     // It is also used in `onCreate()` and `checkPinnedMismatch()`.
186     public static SslCertificate sslCertificate;
187
188     // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`.
189     // It is also used in `onCreate()` and `GetHostIpAddresses()`.
190     public static String currentHostIpAddresses;
191
192     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
193     public static String orbotStatus;
194
195     // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`.  It is also used in `onCreate()`.
196     public static String webViewTitle;
197
198     // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`.  It is also used in `applyDomainSettings()`.
199     public static String appliedUserAgentString;
200
201     // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`
202     public static boolean reloadOnRestart;
203
204     // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment` and `BookmarksActivity`.  It is also used in `onRestart()`.
205     public static boolean loadUrlOnRestart;
206
207     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
208     public static boolean restartFromBookmarksActivity;
209
210     // The block list versions are public static so they can be accessed from `AboutTabFragment`.  They are also used in `onCreate()`.
211     public static String easyListVersion;
212     public static String easyPrivacyVersion;
213     public static String fanboysAnnoyanceVersion;
214     public static String fanboysSocialVersion;
215     public static String ultraPrivacyVersion;
216
217     // The request items are public static so they can be accessed by `BlockListHelper`, `RequestsArrayAdapter`, and `ViewRequestsDialog`.  They are also used in `onCreate()` and `onPrepareOptionsMenu()`.
218     public static List<String[]> resourceRequests;
219     public static String[] whiteListResultStringArray;
220     private int blockedRequests;
221     private int easyListBlockedRequests;
222     private int easyPrivacyBlockedRequests;
223     private int fanboysAnnoyanceListBlockedRequests;
224     private int fanboysSocialBlockingListBlockedRequests;
225     private int ultraPrivacyBlockedRequests;
226     private int thirdPartyBlockedRequests;
227
228     public final static int REQUEST_DISPOSITION = 0;
229     public final static int REQUEST_URL = 1;
230     public final static int REQUEST_BLOCKLIST = 2;
231     public final static int REQUEST_SUBLIST = 3;
232     public final static int REQUEST_BLOCKLIST_ENTRIES = 4;
233     public final static int REQUEST_BLOCKLIST_ORIGINAL_ENTRY = 5;
234
235     public final static int REQUEST_DEFAULT = 0;
236     public final static int REQUEST_ALLOWED = 1;
237     public final static int REQUEST_THIRD_PARTY = 2;
238     public final static int REQUEST_BLOCKED = 3;
239
240     public final static int MAIN_WHITELIST = 1;
241     public final static int FINAL_WHITELIST = 2;
242     public final static int DOMAIN_WHITELIST = 3;
243     public final static int DOMAIN_INITIAL_WHITELIST = 4;
244     public final static int DOMAIN_FINAL_WHITELIST = 5;
245     public final static int THIRD_PARTY_WHITELIST = 6;
246     public final static int THIRD_PARTY_DOMAIN_WHITELIST = 7;
247     public final static int THIRD_PARTY_DOMAIN_INITIAL_WHITELIST = 8;
248
249     public final static int MAIN_BLACKLIST = 9;
250     public final static int INITIAL_BLACKLIST = 10;
251     public final static int FINAL_BLACKLIST = 11;
252     public final static int DOMAIN_BLACKLIST = 12;
253     public final static int DOMAIN_INITIAL_BLACKLIST = 13;
254     public final static int DOMAIN_FINAL_BLACKLIST = 14;
255     public final static int DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 15;
256     public final static int THIRD_PARTY_BLACKLIST = 16;
257     public final static int THIRD_PARTY_INITIAL_BLACKLIST = 17;
258     public final static int THIRD_PARTY_DOMAIN_BLACKLIST = 18;
259     public final static int THIRD_PARTY_DOMAIN_INITIAL_BLACKLIST = 19;
260     public final static int THIRD_PARTY_REGULAR_EXPRESSION_BLACKLIST = 20;
261     public final static int THIRD_PARTY_DOMAIN_REGULAR_EXPRESSION_BLACKLIST = 21;
262     public final static int REGULAR_EXPRESSION_BLACKLIST = 22;
263
264     // `blockAllThirdPartyRequests` is public static so it can be accessed from `RequestsActivity`.
265     // It is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`
266     public static boolean blockAllThirdPartyRequests;
267
268     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
269     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
270     public static String currentBookmarksFolder;
271
272     // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`.  It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
273     public static int domainSettingsDatabaseId;
274
275     // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`.  They are also used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
276     public static String pinnedSslIssuedToCName;
277     public static String pinnedSslIssuedToOName;
278     public static String pinnedSslIssuedToUName;
279     public static String pinnedSslIssuedByCName;
280     public static String pinnedSslIssuedByOName;
281     public static String pinnedSslIssuedByUName;
282     public static Date pinnedSslStartDate;
283     public static Date pinnedSslEndDate;
284     public static String pinnedHostIpAddresses;
285
286     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
287     public final static int UNRECOGNIZED_USER_AGENT = -1;
288     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
289     public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
290     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
291     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
292     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
293
294
295
296     // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`.
297     private static boolean urlIsLoading;
298
299     // `gettingIpAddresses` is used in `onCreate() and `GetHostIpAddresses`.
300     private static boolean gettingIpAddresses;
301
302     // `pinnedDomainSslCertificate` is used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
303     private static boolean pinnedSslCertificate;
304
305     // `pinnedIpAddress` is used in `applyDomainSettings()` and `checkPinnedMismatch()`.
306     private static boolean pinnedIpAddresses;
307
308     // `ignorePinnedDomainInformation` is used in `onSslMismatchProceed()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
309     private static boolean ignorePinnedDomainInformation;
310
311     // `supportFragmentManager` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateContextMenu()`, `onRequestPermissionResult()`, `viewSslCertificate()`,
312     // `applyAppSettings()`, and `checkPinnedMismatch()`.
313     private static FragmentManager supportFragmentManager;
314
315
316     // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
317     private ActionBar appBar;
318
319     // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
320     private boolean navigatingHistory;
321
322     // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
323     private DrawerLayout drawerLayout;
324
325     // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
326     private CoordinatorLayout rootCoordinatorLayout;
327
328     // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
329     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
330     private WebView mainWebView;
331
332     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
333     private FrameLayout fullScreenVideoFrameLayout;
334
335     // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
336     private SwipeRefreshLayout swipeRefreshLayout;
337
338     // `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
339     private RelativeLayout urlAppBarRelativeLayout;
340
341     // `favoriteIconImageView` is used in `onCreate()` and `applyDomainSettings()`
342     private ImageView favoriteIconImageView;
343
344     // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `loadUrlFromTextBox()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`.
345     private CookieManager cookieManager;
346
347     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
348     private final Map<String, String> customHeaders = new HashMap<>();
349
350     // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
351     private boolean javaScriptEnabled;
352
353     // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
354     private boolean firstPartyCookiesEnabled;
355
356     // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
357     private boolean thirdPartyCookiesEnabled;
358
359     // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
360     private boolean domStorageEnabled;
361
362     // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.  It can be removed once the minimum API >= 26.
363     private boolean saveFormDataEnabled;
364
365     // `nightMode` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and  `applyDomainSettings()`.
366     private boolean nightMode;
367
368     // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
369     private String homepage;
370
371     // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
372     private String searchURL;
373
374     // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
375     private Menu mainMenu;
376
377     // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`.
378     private MenuItem refreshMenuItem;
379
380     // The blocklist menu items are used in `onCreate()`, `onCreateOptionsMenu()`, and `onPrepareOptionsMenu()`.
381     private MenuItem blocklistsMenuItem;
382     private MenuItem easyListMenuItem;
383     private MenuItem easyPrivacyMenuItem;
384     private MenuItem fanboysAnnoyanceListMenuItem;
385     private MenuItem fanboysSocialBlockingListMenuItem;
386     private MenuItem ultraPrivacyMenuItem;
387     private MenuItem blockAllThirdPartyRequestsMenuItem;
388
389     // The blocklist variables are used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
390     private boolean easyListEnabled;
391     private boolean easyPrivacyEnabled;
392     private boolean fanboysAnnoyanceListEnabled;
393     private boolean fanboysSocialBlockingListEnabled;
394     private boolean ultraPrivacyEnabled;
395
396     // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
397     private String webViewDefaultUserAgent;
398
399     // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`.
400     private String defaultCustomUserAgentString;
401
402     // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
403     private Runtime privacyBrowserRuntime;
404
405     // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
406     private boolean proxyThroughOrbot;
407
408     // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
409     private boolean incognitoModeEnabled;
410
411     // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
412     private boolean fullScreenBrowsingModeEnabled;
413
414     // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
415     private boolean inFullScreenBrowsingMode;
416
417     // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
418     private boolean hideSystemBarsOnFullscreen;
419
420     // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
421     private boolean translucentNavigationBarOnFullscreen;
422
423     // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
424     private boolean reapplyDomainSettingsOnRestart;
425
426     // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
427     private boolean reapplyAppSettingsOnRestart;
428
429     // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
430     private boolean displayingFullScreenVideo;
431
432     // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
433     private boolean downloadWithExternalApp;
434
435     // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
436     private String currentDomainName;
437
438     // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
439     private BroadcastReceiver orbotStatusBroadcastReceiver;
440
441     // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
442     private boolean waitingForOrbot;
443
444     // `domainSettingsApplied` is used in `prepareOptionsMenu()` and `applyDomainSettings()`.
445     private boolean domainSettingsApplied;
446
447     // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
448     private Boolean domainSettingsJavaScriptEnabled;
449
450     // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
451     private String waitingForOrbotHtmlString;
452
453     // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
454     private String privateDataDirectoryString;
455
456     // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
457     private LinearLayout findOnPageLinearLayout;
458
459     // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
460     private EditText findOnPageEditText;
461
462     // `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
463     private boolean displayAdditionalAppBarIcons;
464
465     // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
466     private ActionBarDrawerToggle drawerToggle;
467
468     // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
469     private Toolbar supportAppBar;
470
471     // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
472     private EditText urlTextBox;
473
474     // The color spans are used in `onCreate()` and `highlightUrlText()`.
475     private ForegroundColorSpan redColorSpan;
476     private ForegroundColorSpan initialGrayColorSpan;
477     private ForegroundColorSpan finalGrayColorSpan;
478
479     // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
480     private int drawerHeaderPaddingLeftAndRight;
481     private int drawerHeaderPaddingTop;
482     private int drawerHeaderPaddingBottom;
483
484     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
485     private SslErrorHandler sslErrorHandler;
486
487     // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
488     private static HttpAuthHandler httpAuthHandler;
489
490     // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
491     private InputMethodManager inputMethodManager;
492
493     // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
494     private RelativeLayout mainWebViewRelativeLayout;
495
496     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
497     // and `loadBookmarksFolder()`.
498     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
499
500     // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
501     private ListView bookmarksListView;
502
503     // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
504     private TextView bookmarksTitleTextView;
505
506     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
507     private Cursor bookmarksCursor;
508
509     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
510     private CursorAdapter bookmarksCursorAdapter;
511
512     // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
513     private String oldFolderNameString;
514
515     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
516     private ValueCallback<Uri[]> fileChooserCallback;
517
518     // The download strings are used in `onCreate()` and `onRequestPermissionResult()`.
519     private String downloadUrl;
520     private String downloadContentDisposition;
521     private long downloadContentLength;
522
523     // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
524     private String downloadImageUrl;
525
526     // The user agent variables are used in `onCreate()` and `applyDomainSettings()`.
527     private ArrayAdapter<CharSequence> userAgentNamesArray;
528     private String[] userAgentDataArray;
529
530     // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, and `onRequestPermissionResult()`.
531     private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
532     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
533
534     @Override
535     // 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.
536     // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
537     @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
538     // Remove Android Studio's warning about deprecations.  The deprecated `getColor()` must be used until API >= 23.
539     @SuppressWarnings("deprecation")
540     protected void onCreate(Bundle savedInstanceState) {
541         // Get a handle for the shared preferences.
542         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
543
544         // Get the theme and screenshot preferences.
545         darkTheme = sharedPreferences.getBoolean("dark_theme", false);
546         allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
547
548         // Disable screenshots if not allowed.
549         if (!allowScreenshots) {
550             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
551         }
552
553         // Set the activity theme.
554         if (darkTheme) {
555             setTheme(R.style.PrivacyBrowserDark);
556         } else {
557             setTheme(R.style.PrivacyBrowserLight);
558         }
559
560         // Run the default commands.
561         super.onCreate(savedInstanceState);
562
563         // Set the content view.
564         setContentView(R.layout.main_drawerlayout);
565
566         // Get a handle for the resources and the support fragment manager.
567         Resources resources = getResources();
568         supportFragmentManager = getSupportFragmentManager();
569
570         // Get a handle for `inputMethodManager`.
571         inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
572
573         // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
574         supportAppBar = findViewById(R.id.app_bar);
575         setSupportActionBar(supportAppBar);
576         appBar = getSupportActionBar();
577
578         // This is needed to get rid of the Android Studio warning that `appBar` might be null.
579         assert appBar != null;
580
581         // Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
582         appBar.setCustomView(R.layout.url_app_bar);
583         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
584
585         // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
586         redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
587         initialGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
588         finalGrayColorSpan = new ForegroundColorSpan(resources.getColor(R.color.gray_500));
589
590         // Get a handle for `urlTextBox`.
591         urlTextBox = findViewById(R.id.url_edittext);
592
593         // Remove the formatting from `urlTextBar` when the user is editing the text.
594         urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
595             if (hasFocus) {  // The user is editing the URL text box.
596                 // Remove the highlighting.
597                 urlTextBox.getText().removeSpan(redColorSpan);
598                 urlTextBox.getText().removeSpan(initialGrayColorSpan);
599                 urlTextBox.getText().removeSpan(finalGrayColorSpan);
600             } else {  // The user has stopped editing the URL text box.
601                 // Move to the beginning of the string.
602                 urlTextBox.setSelection(0);
603
604                 // Reapply the highlighting.
605                 highlightUrlText();
606             }
607         });
608
609         // Set the go button on the keyboard to load the URL in `urlTextBox`.
610         urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
611             // If the event is a key-down event on the `enter` button, load the URL.
612             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
613                 // Load the URL into the mainWebView and consume the event.
614                 loadUrlFromTextBox();
615
616                 // If the enter key was pressed, consume the event.
617                 return true;
618             } else {
619                 // If any other key was pressed, do not consume the event.
620                 return false;
621             }
622         });
623
624         // Set `waitingForOrbotHTMLString`.
625         waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
626
627         // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`.
628         currentDomainName = "";
629         orbotStatus = "unknown";
630         waitingForOrbot = false;
631
632         // Create an Orbot status `BroadcastReceiver`.
633         orbotStatusBroadcastReceiver = new BroadcastReceiver() {
634             @Override
635             public void onReceive(Context context, Intent intent) {
636                 // Store the content of the status message in `orbotStatus`.
637                 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
638
639                 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
640                 if (orbotStatus.equals("ON") && waitingForOrbot) {
641                     // Reset `waitingForOrbot`.
642                     waitingForOrbot = false;
643
644                     // Load `formattedUrlString
645                     loadUrl(formattedUrlString);
646                 }
647             }
648         };
649
650         // Register `orbotStatusBroadcastReceiver` on `this` context.
651         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
652
653         // Get handles for views that need to be accessed.
654         drawerLayout = findViewById(R.id.drawerlayout);
655         rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
656         bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
657         bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
658         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
659         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
660         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
661         mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
662         mainWebView = findViewById(R.id.main_webview);
663         findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
664         findOnPageEditText = findViewById(R.id.find_on_page_edittext);
665         fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
666         urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
667         favoriteIconImageView = findViewById(R.id.favorite_icon);
668
669         // Set the bookmarks drawer resources according to the theme.  This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
670         if (darkTheme) {
671             launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_dark));
672             createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_dark));
673             createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_dark));
674             bookmarksListView.setBackgroundColor(resources.getColor(R.color.gray_850));
675         } else {
676             launchBookmarksActivityFab.setImageDrawable(resources.getDrawable(R.drawable.bookmarks_light));
677             createBookmarkFolderFab.setImageDrawable(resources.getDrawable(R.drawable.create_folder_light));
678             createBookmarkFab.setImageDrawable(resources.getDrawable(R.drawable.create_bookmark_light));
679             bookmarksListView.setBackgroundColor(resources.getColor(R.color.white));
680         }
681
682         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
683         launchBookmarksActivityFab.setOnClickListener(v -> {
684             // Create an intent to launch the bookmarks activity.
685             Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
686
687             // Include the current folder with the `Intent`.
688             bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
689
690             // Make it so.
691             startActivity(bookmarksIntent);
692         });
693
694         // Set the create new bookmark folder FAB to display an alert dialog.
695         createBookmarkFolderFab.setOnClickListener(v -> {
696             // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
697             AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
698             createBookmarkFolderDialog.show(supportFragmentManager, resources.getString(R.string.create_folder));
699         });
700
701         // Set the create new bookmark FAB to display an alert dialog.
702         createBookmarkFab.setOnClickListener(view -> {
703             // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
704             AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
705             createBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.create_bookmark));
706         });
707
708         // Create a double-tap listener to toggle full-screen mode.
709         final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
710             // Override `onDoubleTap()`.  All other events are handled using the default settings.
711             @Override
712             public boolean onDoubleTap(MotionEvent event) {
713                 if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
714                     // Toggle `inFullScreenBrowsingMode`.
715                     inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
716
717                     if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
718                         // Hide the `appBar`.
719                         appBar.hide();
720
721                         // Hide the banner ad in the free flavor.
722                         if (BuildConfig.FLAVOR.contentEquals("free")) {
723                             // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
724                             AdHelper.hideAd(findViewById(R.id.adview));
725                         }
726
727                         // Modify the system bars.
728                         if (hideSystemBarsOnFullscreen) {  // Hide everything.
729                             // Remove the translucent overlays.
730                             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
731
732                             // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
733                             drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
734
735                             /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
736                              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
737                              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
738                              */
739                             rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
740
741                             // Set `rootCoordinatorLayout` to fill the whole screen.
742                             rootCoordinatorLayout.setFitsSystemWindows(false);
743                         } else {  // Hide everything except the status and navigation bars.
744                             // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
745                             rootCoordinatorLayout.setFitsSystemWindows(false);
746
747                             // 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.
748                             if (translucentNavigationBarOnFullscreen) {
749                                 // Set the navigation bar to be translucent.
750                                 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
751                             }
752                         }
753                     } else {  // Switch to normal viewing mode.
754                         // Show the `appBar`.
755                         appBar.show();
756
757                         // Show the `BannerAd` in the free flavor.
758                         if (BuildConfig.FLAVOR.contentEquals("free")) {
759                             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
760                             AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
761                         }
762
763                         // Remove the translucent navigation bar flag if it is set.
764                         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
765
766                         // Add the translucent status flag if it is unset.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
767                         getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
768
769                         // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
770                         rootCoordinatorLayout.setSystemUiVisibility(0);
771
772                         // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
773                         rootCoordinatorLayout.setFitsSystemWindows(true);
774                     }
775
776                     // Consume the double-tap.
777                     return true;
778                 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
779                     return false;
780                 }
781             }
782         });
783
784         // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
785         mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
786             // Call `performClick()` on the view, which is required for accessibility.
787             v.performClick();
788
789             // Send the `event` to `gestureDetector`.
790             return gestureDetector.onTouchEvent(event);
791         });
792
793         // Update `findOnPageCountTextView`.
794         mainWebView.setFindListener(new WebView.FindListener() {
795             // Get a handle for `findOnPageCountTextView`.
796             final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
797
798             @Override
799             public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
800                 if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
801                     // Set `findOnPageCountTextView` to `0/0`.
802                     findOnPageCountTextView.setText(R.string.zero_of_zero);
803                 } else if (isDoneCounting) {  // There are matches.
804                     // `activeMatchOrdinal` is zero-based.
805                     int activeMatch = activeMatchOrdinal + 1;
806
807                     // Build the match string.
808                     String matchString = activeMatch + "/" + numberOfMatches;
809
810                     // Set `findOnPageCountTextView`.
811                     findOnPageCountTextView.setText(matchString);
812                 }
813             }
814         });
815
816         // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
817         findOnPageEditText.addTextChangedListener(new TextWatcher() {
818             @Override
819             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
820                 // Do nothing.
821             }
822
823             @Override
824             public void onTextChanged(CharSequence s, int start, int before, int count) {
825                 // Do nothing.
826             }
827
828             @Override
829             public void afterTextChanged(Editable s) {
830                 // Search for the text in `mainWebView`.
831                 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
832             }
833         });
834
835         // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
836         findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
837             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
838                 // Hide the soft keyboard.  `0` indicates no additional flags.
839                 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
840
841                 // Consume the event.
842                 return true;
843             } else {  // A different key was pressed.
844                 // Do not consume the event.
845                 return false;
846             }
847         });
848
849         // Implement swipe to refresh.
850         swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
851         swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
852
853         // Set the swipe to refresh color according to the theme.
854         if (darkTheme) {
855             swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
856             swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
857         } else {
858             swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
859         }
860
861         // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
862         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
863         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
864
865         // Listen for touches on the navigation menu.
866         final NavigationView navigationView = findViewById(R.id.navigationview);
867         navigationView.setNavigationItemSelectedListener(this);
868
869         // 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.
870         final Menu navigationMenu = navigationView.getMenu();
871         final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
872         final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
873         final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
874         final MenuItem navigationRequestsMenuItem = navigationMenu.getItem(4);
875
876         // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
877         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
878
879         // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
880         currentBookmarksFolder = "";
881
882         // Load the home folder, which is `""` in the database.
883         loadBookmarksFolder();
884
885         bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
886             // Convert the id from long to int to match the format of the bookmarks database.
887             int databaseID = (int) id;
888
889             // Get the bookmark cursor for this ID and move it to the first row.
890             Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
891             bookmarkCursor.moveToFirst();
892
893             // Act upon the bookmark according to the type.
894             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
895                 // Store the new folder name in `currentBookmarksFolder`.
896                 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
897
898                 // Load the new folder.
899                 loadBookmarksFolder();
900             } else {  // The selected bookmark is not a folder.
901                 // Load the bookmark URL.
902                 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
903
904                 // Close the bookmarks drawer.
905                 drawerLayout.closeDrawer(GravityCompat.END);
906             }
907
908             // Close the `Cursor`.
909             bookmarkCursor.close();
910         });
911
912         bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
913             // Convert the database ID from `long` to `int`.
914             int databaseId = (int) id;
915
916             // Find out if the selected bookmark is a folder.
917             boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
918
919             if (isFolder) {
920                 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
921                 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
922
923                 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
924                 AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
925                 editFolderDialog.show(supportFragmentManager, resources.getString(R.string.edit_folder));
926             } else {
927                 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
928                 AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
929                 editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark));
930             }
931
932             // Consume the event.
933             return true;
934         });
935
936         // Get the status bar pixel size.
937         int statusBarResourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
938         int statusBarPixelSize = resources.getDimensionPixelSize(statusBarResourceId);
939
940         // Get the resource density.
941         float screenDensity = resources.getDisplayMetrics().density;
942
943         // Calculate the drawer header padding.  This is used to move the text in the drawer headers below any cutouts.
944         drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
945         drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
946         drawerHeaderPaddingBottom = (int) (8 * screenDensity);
947
948         // The drawer listener is used to update the navigation menu.`
949         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
950             @Override
951             public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
952             }
953
954             @Override
955             public void onDrawerOpened(@NonNull View drawerView) {
956             }
957
958             @Override
959             public void onDrawerClosed(@NonNull View drawerView) {
960             }
961
962             @Override
963             public void onDrawerStateChanged(int newState) {
964                 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) {  // A drawer is opening or closing.
965                     // Get handles for the drawer headers.
966                     TextView navigationHeaderTextView = findViewById(R.id.navigationText);
967                     TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
968
969                     // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
970                     if (navigationHeaderTextView != null) {
971                         navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
972                     }
973
974                     // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
975                     if (bookmarksHeaderTextView != null) {
976                         bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
977                     }
978
979                     // Update the back, forward, history, and requests menu items.
980                     navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
981                     navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
982                     navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
983                     navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
984
985                     // Hide the keyboard (if displayed).
986                     inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
987
988                     // Clear the focus from from the URL text box and the WebView.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
989                     urlTextBox.clearFocus();
990                     mainWebView.clearFocus();
991                 }
992             }
993         });
994
995         // drawerToggle creates the hamburger icon at the start of the AppBar.
996         drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
997
998         // Get a handle for the progress bar.
999         final ProgressBar progressBar = findViewById(R.id.progress_bar);
1000
1001         mainWebView.setWebChromeClient(new WebChromeClient() {
1002             // Update the progress bar when a page is loading.
1003             @Override
1004             public void onProgressChanged(WebView view, int progress) {
1005                 // Inject the night mode CSS if night mode is enabled.
1006                 if (nightMode) {
1007                     // `background-color: #212121` sets the background to be dark gray.  `color: #BDBDBD` sets the text color to be light gray.  `box-shadow: none` removes a lower underline on links
1008                     // used by WordPress.  `text-decoration: none` removes all text underlines.  `text-shadow: none` removes text shadows, which usually have a hard coded color.
1009                     // `border: none` removes all borders, which can also be used to underline text.  `a {color: #1565C0}` sets links to be a dark blue.
1010                     // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
1011                     mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
1012                             "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
1013                             "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
1014                                 // Initialize a handler to display `mainWebView`.
1015                                 Handler displayWebViewHandler = new Handler();
1016
1017                                 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
1018                                 Runnable displayWebViewRunnable = () -> {
1019                                     // Only display `mainWebView` if the progress bar is gone.  This prevents the display of the `WebView` while it is still loading.
1020                                     if (progressBar.getVisibility() == View.GONE) {
1021                                         mainWebView.setVisibility(View.VISIBLE);
1022                                     }
1023                                 };
1024
1025                                 // Displaying of `mainWebView` after 500 milliseconds.
1026                                 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
1027                             });
1028                 }
1029
1030                 // Update the progress bar.
1031                 progressBar.setProgress(progress);
1032
1033                 // Set the visibility of the progress bar.
1034                 if (progress < 100) {
1035                     // Show the progress bar.
1036                     progressBar.setVisibility(View.VISIBLE);
1037                 } else {
1038                     // Hide the progress bar.
1039                     progressBar.setVisibility(View.GONE);
1040
1041                     // Display `mainWebView` if night mode is disabled.
1042                     // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not
1043                     // currently enabled.
1044                     if (!nightMode) {
1045                         mainWebView.setVisibility(View.VISIBLE);
1046                     }
1047
1048                     //Stop the swipe to refresh indicator if it is running
1049                     swipeRefreshLayout.setRefreshing(false);
1050                 }
1051             }
1052
1053             // Set the favorite icon when it changes.
1054             @Override
1055             public void onReceivedIcon(WebView view, Bitmap icon) {
1056                 // Only update the favorite icon if the website has finished loading.
1057                 if (progressBar.getVisibility() == View.GONE) {
1058                     // Save a copy of the favorite icon.
1059                     favoriteIconBitmap = icon;
1060
1061                     // Place the favorite icon in the appBar.
1062                     favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
1063                 }
1064             }
1065
1066             // Save a copy of the title when it changes.
1067             @Override
1068             public void onReceivedTitle(WebView view, String title) {
1069                 // Save a copy of the title.
1070                 webViewTitle = title;
1071             }
1072
1073             // Enter full screen video.
1074             @Override
1075             public void onShowCustomView(View view, CustomViewCallback callback) {
1076                 // Set the full screen video flag.
1077                 displayingFullScreenVideo = true;
1078
1079                 // Pause the ad if this is the free flavor.
1080                 if (BuildConfig.FLAVOR.contentEquals("free")) {
1081                     // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1082                     AdHelper.pauseAd(findViewById(R.id.adview));
1083                 }
1084
1085                 // Remove the translucent overlays.
1086                 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1087
1088                 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1089                 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1090
1091                 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1092                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1093                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1094                  */
1095                 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1096
1097                 // Set `rootCoordinatorLayout` to fill the entire screen.
1098                 rootCoordinatorLayout.setFitsSystemWindows(false);
1099
1100                 // Disable the sliding drawers.
1101                 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1102
1103                 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
1104                 fullScreenVideoFrameLayout.addView(view);
1105                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
1106             }
1107
1108             // Exit full screen video.
1109             @Override
1110             public void onHideCustomView() {
1111                 // Unset the full screen video flag.
1112                 displayingFullScreenVideo = false;
1113
1114                 // Hide `fullScreenVideoFrameLayout`.
1115                 fullScreenVideoFrameLayout.removeAllViews();
1116                 fullScreenVideoFrameLayout.setVisibility(View.GONE);
1117
1118                 // Enable the sliding drawers.
1119                 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
1120
1121                 // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
1122                 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
1123                     if (hideSystemBarsOnFullscreen) {  // Hide everything.
1124                         // Remove the translucent navigation setting if it is currently flagged.
1125                         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1126
1127                         // Remove the translucent status bar overlay.
1128                         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1129
1130                         // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1131                         drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1132
1133                         /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1134                          * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1135                          * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1136                          */
1137                         rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1138                     } else {  // Hide everything except the status and navigation bars.
1139                         // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1140                         rootCoordinatorLayout.setSystemUiVisibility(0);
1141
1142                         // Add the translucent status flag if it is unset.
1143                         getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1144
1145                         if (translucentNavigationBarOnFullscreen) {
1146                             // Set the navigation bar to be translucent.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1147                             getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1148                         } else {
1149                             // Set the navigation bar to be black.
1150                             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1151                         }
1152                     }
1153                 } else {  // Switch to normal viewing mode.
1154                     // Show the `appBar` if `findOnPageLinearLayout` is not visible.
1155                     if (findOnPageLinearLayout.getVisibility() == View.GONE) {
1156                         appBar.show();
1157                     }
1158
1159                     // Show the `BannerAd` in the free flavor.
1160                     if (BuildConfig.FLAVOR.contentEquals("free")) {
1161                         // Initialize the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1162                         AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
1163                     }
1164
1165                     // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
1166                     rootCoordinatorLayout.setSystemUiVisibility(0);
1167
1168                     // Remove the translucent navigation bar flag if it is set.
1169                     getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1170
1171                     // Add the translucent status flag if it is unset.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
1172                     getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1173
1174                     // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
1175                     rootCoordinatorLayout.setFitsSystemWindows(true);
1176                 }
1177
1178                 // Show the ad if this is the free flavor.
1179                 if (BuildConfig.FLAVOR.contentEquals("free")) {
1180                     // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1181                     AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1182                 }
1183             }
1184
1185             // Upload files.
1186             @Override
1187             public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
1188                 // Show the file chooser if the device is running API >= 21.
1189                 if (Build.VERSION.SDK_INT >= 21) {
1190                     // Store the file path callback.
1191                     fileChooserCallback = filePathCallback;
1192
1193                     // Create an intent to open a chooser based ont the file chooser parameters.
1194                     Intent fileChooserIntent = fileChooserParams.createIntent();
1195
1196                     // Open the file chooser.  Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
1197                     startActivityForResult(fileChooserIntent, 0);
1198                 }
1199                 return true;
1200             }
1201         });
1202
1203         // Register `mainWebView` for a context menu.  This is used to see link targets and download images.
1204         registerForContextMenu(mainWebView);
1205
1206         // Allow the downloading of files.
1207         mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
1208             // Check if the download should be processed by an external app.
1209             if (downloadWithExternalApp) {  // Download with an external app.
1210                 openUrlWithExternalApp(url);
1211             } else {  // Download with Android's download manager.
1212                 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
1213                 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission has not been granted.
1214                     // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
1215
1216                     // Store the variables for future use by `onRequestPermissionsResult()`.
1217                     downloadUrl = url;
1218                     downloadContentDisposition = contentDisposition;
1219                     downloadContentLength = contentLength;
1220
1221                     // Show a dialog if the user has previously denied the permission.
1222                     if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
1223                         // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1224                         DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1225
1226                         // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
1227                         downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
1228                     } else {  // Show the permission request directly.
1229                         // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
1230                         ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1231                     }
1232                 } else {  // The storage permission has already been granted.
1233                     // Get a handle for the download file alert dialog.
1234                     AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
1235
1236                     // Show the download file alert dialog.
1237                     downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download));
1238                 }
1239             }
1240         });
1241
1242         // Allow pinch to zoom.
1243         mainWebView.getSettings().setBuiltInZoomControls(true);
1244
1245         // Hide zoom controls.
1246         mainWebView.getSettings().setDisplayZoomControls(false);
1247
1248         // Don't allow mixed content (HTTP and HTTPS) on the same website.
1249         if (Build.VERSION.SDK_INT >= 21) {
1250             mainWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
1251         }
1252
1253         // Set the WebView to use a wide viewport.  Otherwise, some web pages will be scrunched and some content will render outside the screen.
1254         mainWebView.getSettings().setUseWideViewPort(true);
1255
1256         // Set the WebView to load in overview mode (zoomed out to the maximum width).
1257         mainWebView.getSettings().setLoadWithOverviewMode(true);
1258
1259         // Explicitly disable geolocation.
1260         mainWebView.getSettings().setGeolocationEnabled(false);
1261
1262         // Initialize cookieManager.
1263         cookieManager = CookieManager.getInstance();
1264
1265         // 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).
1266         customHeaders.put("X-Requested-With", "");
1267
1268         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
1269         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
1270
1271         // Get a handle for the `Runtime`.
1272         privacyBrowserRuntime = Runtime.getRuntime();
1273
1274         // Store the application's private data directory.
1275         privateDataDirectoryString = getApplicationInfo().dataDir;
1276         // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1277
1278         // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
1279         inFullScreenBrowsingMode = false;
1280
1281         // Initialize the privacy settings variables.
1282         javaScriptEnabled = false;
1283         firstPartyCookiesEnabled = false;
1284         thirdPartyCookiesEnabled = false;
1285         domStorageEnabled = false;
1286         saveFormDataEnabled = false;  // Form data can be removed once the minimum API >= 26.
1287         nightMode = false;
1288
1289         // Store the default user agent.
1290         webViewDefaultUserAgent = mainWebView.getSettings().getUserAgentString();
1291
1292         // Initialize the WebView title.
1293         webViewTitle = getString(R.string.no_title);
1294
1295         // Initialize the favorite icon bitmap.  `ContextCompat` must be used until API >= 21.
1296         Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
1297         BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
1298         assert favoriteIconBitmapDrawable != null;
1299         favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
1300
1301         // If the favorite icon is null, load the default.
1302         if (favoriteIconBitmap == null) {
1303             favoriteIconBitmap = favoriteIconDefaultBitmap;
1304         }
1305
1306         // Initialize the user agent array adapter and string array.
1307         userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
1308         userAgentDataArray = resources.getStringArray(R.array.user_agent_data);
1309
1310         // Apply the app settings from the shared preferences.
1311         applyAppSettings();
1312
1313         // Instantiate the block list helper.
1314         BlockListHelper blockListHelper = new BlockListHelper();
1315
1316         // Initialize the list of resource requests.
1317         resourceRequests = new ArrayList<>();
1318
1319         // Parse the block lists.
1320         final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
1321         final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
1322         final ArrayList<List<String[]>> fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
1323         final ArrayList<List<String[]>> fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
1324         final ArrayList<List<String[]>> ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
1325
1326         // Store the list versions.
1327         easyListVersion = easyList.get(0).get(0)[0];
1328         easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
1329         fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
1330         fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
1331         ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
1332
1333         // Get a handle for the activity.  This is used to update the requests counter while the navigation menu is open.
1334         Activity activity = this;
1335
1336         mainWebView.setWebViewClient(new WebViewClient() {
1337             // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
1338             // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
1339             @SuppressWarnings("deprecation")
1340             @Override
1341             public boolean shouldOverrideUrlLoading(WebView view, String url) {
1342                 if (url.startsWith("http")) {  // Load the URL in Privacy Browser.
1343                     // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
1344                     formattedUrlString = "";
1345
1346                     // Apply the domain settings for the new URL.  `applyDomainSettings` doesn't do anything if the domain has not changed.
1347                     boolean userAgentChanged = applyDomainSettings(url, true, false);
1348
1349                     // Check if the user agent has changed.
1350                     if (userAgentChanged) {
1351                         // Manually load the URL.  The changing of the user agent will cause WebView to reload the previous URL.
1352                         mainWebView.loadUrl(url, customHeaders);
1353
1354                         // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
1355                         return true;
1356                     } else {
1357                         // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
1358                         return false;
1359                     }
1360                 } else if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
1361                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1362                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1363
1364                     // Parse the url and set it as the data for the intent.
1365                     emailIntent.setData(Uri.parse(url));
1366
1367                     // Open the email program in a new task instead of as part of Privacy Browser.
1368                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1369
1370                     // Make it so.
1371                     startActivity(emailIntent);
1372
1373                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1374                     return true;
1375                 } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
1376                     // Open the dialer and load the phone number, but wait for the user to place the call.
1377                     Intent dialIntent = new Intent(Intent.ACTION_DIAL);
1378
1379                     // Add the phone number to the intent.
1380                     dialIntent.setData(Uri.parse(url));
1381
1382                     // Open the dialer in a new task instead of as part of Privacy Browser.
1383                     dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1384
1385                     // Make it so.
1386                     startActivity(dialIntent);
1387
1388                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1389                     return true;
1390                 } else {  // Load a system chooser to select an app that can handle the URL.
1391                     // Open an app that can handle the URL.
1392                     Intent genericIntent = new Intent(Intent.ACTION_VIEW);
1393
1394                     // Add the URL to the intent.
1395                     genericIntent.setData(Uri.parse(url));
1396
1397                     // List all apps that can handle the URL instead of just opening the first one.
1398                     genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
1399
1400                     // Open the app in a new task instead of as part of Privacy Browser.
1401                     genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1402
1403                     // Start the app or display a snackbar if no app is available to handle the URL.
1404                     try {
1405                         startActivity(genericIntent);
1406                     } catch (ActivityNotFoundException exception) {
1407                         Snackbar.make(mainWebView, getString(R.string.unrecognized_url) + "  " + url, Snackbar.LENGTH_SHORT).show();
1408                     }
1409
1410                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
1411                     return true;
1412                 }
1413             }
1414
1415             // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
1416             @SuppressWarnings("deprecation")
1417             @Override
1418             public WebResourceResponse shouldInterceptRequest(WebView view, String url){
1419                 // Create an empty web resource response to be used if the resource request is blocked.
1420                 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
1421
1422                 // Reset the whitelist results tracker.
1423                 whiteListResultStringArray = null;
1424
1425                 // Initialize the third party request tracker.
1426                 boolean isThirdPartyRequest = false;
1427
1428                 // Initialize the current domain string.
1429                 String currentDomain = "";
1430
1431                 // Nobody is happy when comparing null strings.
1432                 if (!(formattedUrlString == null) && !(url == null)) {
1433                     // Get the domain strings to URIs.
1434                     Uri currentDomainUri = Uri.parse(formattedUrlString);
1435                     Uri requestDomainUri = Uri.parse(url);
1436
1437                     // Get the domain host names.
1438                     String currentBaseDomain = currentDomainUri.getHost();
1439                     String requestBaseDomain = requestDomainUri.getHost();
1440
1441                     // Update the current domain variable.
1442                     currentDomain = currentBaseDomain;
1443
1444                     // Only compare the current base domain and the request base domain if neither is null.
1445                     if (!(currentBaseDomain == null) && !(requestBaseDomain == null)) {
1446                         // Determine the current base domain.
1447                         while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
1448                             // Remove the first subdomain.
1449                             currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
1450                         }
1451
1452                         // Determine the request base domain.
1453                         while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
1454                             // Remove the first subdomain.
1455                             requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
1456                         }
1457
1458                         // Update the third party request tracker.
1459                         isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
1460                     }
1461                 }
1462
1463                 // Block third-party requests if enabled.
1464                 if (isThirdPartyRequest && blockAllThirdPartyRequests) {
1465                     // Increment the blocked requests counters.
1466                     blockedRequests++;
1467                     thirdPartyBlockedRequests++;
1468
1469                     // Update the titles of the blocklist menu items.  This must be run from the UI thread.
1470                     activity.runOnUiThread(() -> {
1471                         navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1472                         blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1473                         blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
1474                     });
1475
1476                     // Add the request to the log.
1477                     resourceRequests.add(new String[]{String.valueOf(REQUEST_THIRD_PARTY), url});
1478
1479                     // Return an empty web resource response.
1480                     return emptyWebResourceResponse;
1481                 }
1482
1483                 // Check UltraPrivacy if it is enabled.
1484                 if (ultraPrivacyEnabled) {
1485                     if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, ultraPrivacy)) {
1486                         // Increment the blocked requests counters.
1487                         blockedRequests++;
1488                         ultraPrivacyBlockedRequests++;
1489
1490                         // Update the titles of the blocklist menu items.  This must be run from the UI thread.
1491                         activity.runOnUiThread(() -> {
1492                             navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1493                             blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1494                             ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
1495                         });
1496
1497                         // The resource request was blocked.  Return an empty web resource response.
1498                         return emptyWebResourceResponse;
1499                     }
1500
1501                     // If the whitelist result is not null, the request has been allowed by UltraPrivacy.
1502                     if (whiteListResultStringArray != null) {
1503                         // Add a whitelist entry to the resource requests array.
1504                         resourceRequests.add(whiteListResultStringArray);
1505
1506                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
1507                         return null;
1508                     }
1509                 }
1510
1511                 // Check EasyList if it is enabled.
1512                 if (easyListEnabled) {
1513                     if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyList)) {
1514                         // Increment the blocked requests counters.
1515                         blockedRequests++;
1516                         easyListBlockedRequests++;
1517
1518                         // Update the titles of the blocklist menu items.  This must be run from the UI thread.
1519                         activity.runOnUiThread(() -> {
1520                             navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1521                             blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1522                             easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
1523                         });
1524
1525                         // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1526                         whiteListResultStringArray = null;
1527
1528                         // The resource request was blocked.  Return an empty web resource response.
1529                         return emptyWebResourceResponse;
1530                     }
1531                 }
1532
1533                 // Check EasyPrivacy if it is enabled.
1534                 if (easyPrivacyEnabled) {
1535                     if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, easyPrivacy)) {
1536                         // Increment the blocked requests counters.
1537                         blockedRequests++;
1538                         easyPrivacyBlockedRequests++;
1539
1540                         // Update the titles of the blocklist menu items.  This must be run from the UI thread.
1541                         activity.runOnUiThread(() -> {
1542                             navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1543                             blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1544                             easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
1545                         });
1546
1547                         // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1548                         whiteListResultStringArray = null;
1549
1550                         // The resource request was blocked.  Return an empty web resource response.
1551                         return emptyWebResourceResponse;
1552                     }
1553                 }
1554
1555                 // Check Fanboy’s Annoyance List if it is enabled.
1556                 if (fanboysAnnoyanceListEnabled) {
1557                     if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList)) {
1558                         // Increment the blocked requests counters.
1559                         blockedRequests++;
1560                         fanboysAnnoyanceListBlockedRequests++;
1561
1562                         // Update the titles of the blocklist menu items.  This must be run from the UI thread.
1563                         activity.runOnUiThread(() -> {
1564                             navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1565                             blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1566                             fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
1567                         });
1568
1569                         // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1570                         whiteListResultStringArray = null;
1571
1572                         // The resource request was blocked.  Return an empty web resource response.
1573                         return emptyWebResourceResponse;
1574                     }
1575                 } else if (fanboysSocialBlockingListEnabled){  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
1576                     if (blockListHelper.isBlocked(currentDomain, url, isThirdPartyRequest, fanboysSocialList)) {
1577                         // Increment the blocked requests counters.
1578                         blockedRequests++;
1579                         fanboysSocialBlockingListBlockedRequests++;
1580
1581                         // Update the titles of the blocklist menu items.  This must be run from the UI thread.
1582                         activity.runOnUiThread(() -> {
1583                             navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1584                             blocklistsMenuItem.setTitle(getString(R.string.requests) + " - " + blockedRequests);
1585                             fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
1586                         });
1587
1588                         // Reset the whitelist results tracker (because otherwise it will sometimes add results to the list due to a race condition).
1589                         whiteListResultStringArray = null;
1590
1591                         // The resource request was blocked.  Return an empty web resource response.
1592                         return emptyWebResourceResponse;
1593                     }
1594                 }
1595
1596                 // Add the request to the log because it hasn't been processed by any of the previous checks.
1597                 if (whiteListResultStringArray != null ) {  // The request was processed by a whitelist.
1598                     resourceRequests.add(whiteListResultStringArray);
1599                 } else {  // The request didn't match any blocklist entry.  Log it as a default request.
1600                     resourceRequests.add(new String[]{String.valueOf(REQUEST_DEFAULT), url});
1601                 }
1602
1603                 // The resource request has not been blocked.  `return null` loads the requested resource.
1604                 return null;
1605             }
1606
1607             // Handle HTTP authentication requests.
1608             @Override
1609             public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
1610                 // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
1611                 httpAuthHandler = handler;
1612
1613                 // Display the HTTP authentication dialog.
1614                 AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
1615                 httpAuthenticationDialogFragment.show(supportFragmentManager, getString(R.string.http_authentication));
1616             }
1617
1618             // Update the URL in urlTextBox when the page starts to load.
1619             @Override
1620             public void onPageStarted(WebView view, String url, Bitmap favicon) {
1621                 // 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.
1622                 // This is also used to determine when to check for pinned mismatches.
1623                 urlIsLoading = true;
1624
1625                 // Reset the list of host IP addresses.
1626                 currentHostIpAddresses = "";
1627
1628                 // Reset the list of resource requests.
1629                 resourceRequests.clear();
1630
1631                 // Initialize the counters for requests blocked by each blocklist.
1632                 blockedRequests = 0;
1633                 easyListBlockedRequests = 0;
1634                 easyPrivacyBlockedRequests = 0;
1635                 fanboysAnnoyanceListBlockedRequests = 0;
1636                 fanboysSocialBlockingListBlockedRequests = 0;
1637                 ultraPrivacyBlockedRequests = 0;
1638                 thirdPartyBlockedRequests = 0;
1639
1640                 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
1641                 if (nightMode) {
1642                     mainWebView.setVisibility(View.INVISIBLE);
1643                 }
1644
1645                 // Hide the keyboard.  `0` indicates no additional flags.
1646                 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1647
1648                 // Check to see if Privacy Browser is waiting on Orbot.
1649                 if (!waitingForOrbot) {  // Process the URL.
1650                     // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
1651                     formattedUrlString = url;
1652
1653                     // Display the formatted URL text.
1654                     urlTextBox.setText(formattedUrlString);
1655
1656                     // Apply text highlighting to `urlTextBox`.
1657                     highlightUrlText();
1658
1659                     // Get a URI for the current URL.
1660                     Uri currentUri = Uri.parse(formattedUrlString);
1661
1662                     // Get the IP addresses for the host.
1663                     new GetHostIpAddresses(activity).execute(currentUri.getHost());
1664
1665                     // Apply any custom domain settings if the URL was loaded by navigating history.
1666                     if (navigatingHistory) {
1667                         // Apply the domain settings.
1668                         boolean userAgentChanged = applyDomainSettings(url, true, false);
1669
1670                         // Reset `navigatingHistory`.
1671                         navigatingHistory = false;
1672
1673                         // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
1674                         if (userAgentChanged) {
1675                             loadUrl(formattedUrlString);
1676                         }
1677                     }
1678
1679                     // Replace Refresh with Stop if the menu item has been created.  (The WebView typically begins loading before the menu items are instantiated.)
1680                     if (refreshMenuItem != null) {
1681                         // Set the title.
1682                         refreshMenuItem.setTitle(R.string.stop);
1683
1684                         // If the icon is displayed in the AppBar, set it according to the theme.
1685                         if (displayAdditionalAppBarIcons) {
1686                             if (darkTheme) {
1687                                 refreshMenuItem.setIcon(R.drawable.close_dark);
1688                             } else {
1689                                 refreshMenuItem.setIcon(R.drawable.close_light);
1690                             }
1691                         }
1692                     }
1693                 }
1694             }
1695
1696             // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
1697             @Override
1698             public void onPageFinished(WebView view, String url) {
1699                 // Reset the wide view port if it has been turned off by the waiting for Orbot message.
1700                 if (!waitingForOrbot) {
1701                     // Only use a wide view port if the URL starts with `http`, not for `file://` and `content://`.
1702                     mainWebView.getSettings().setUseWideViewPort(url.startsWith("http"));
1703                 }
1704
1705                 // Flush any cookies to persistent storage.  `CookieManager` has become very lazy about flushing cookies in recent versions.
1706                 if (firstPartyCookiesEnabled && Build.VERSION.SDK_INT >= 21) {
1707                     cookieManager.flush();
1708                 }
1709
1710                 // Update the Refresh menu item if it has been created.
1711                 if (refreshMenuItem != null) {
1712                     // Reset the Refresh title.
1713                     refreshMenuItem.setTitle(R.string.refresh);
1714
1715                     // If the icon is displayed in the AppBar, reset it according to the theme.
1716                     if (displayAdditionalAppBarIcons) {
1717                         if (darkTheme) {
1718                             refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
1719                         } else {
1720                             refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
1721                         }
1722                     }
1723                 }
1724
1725
1726
1727                 // Clear the cache and history if Incognito Mode is enabled.
1728                 if (incognitoModeEnabled) {
1729                     // Clear the cache.  `true` includes disk files.
1730                     mainWebView.clearCache(true);
1731
1732                     // Clear the back/forward history.
1733                     mainWebView.clearHistory();
1734
1735                     // Manually delete cache folders.
1736                     try {
1737                         // Delete the main cache directory.
1738                         privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
1739
1740                         // Delete the secondary `Service Worker` cache directory.
1741                         // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
1742                         privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
1743                     } catch (IOException e) {
1744                         // Do nothing if an error is thrown.
1745                     }
1746                 }
1747
1748                 // Update the URL text box and apply domain settings if not waiting on Orbot.
1749                 if (!waitingForOrbot) {
1750                     // Check to see if `WebView` has set `url` to be `about:blank`.
1751                     if (url.equals("about:blank")) {  // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
1752                         // Set `formattedUrlString` to `""`.
1753                         formattedUrlString = "";
1754
1755                         urlTextBox.setText(formattedUrlString);
1756
1757                         // Request focus for `urlTextBox`.
1758                         urlTextBox.requestFocus();
1759
1760                         // Display the keyboard.
1761                         inputMethodManager.showSoftInput(urlTextBox, 0);
1762
1763                         // Apply the domain settings.  This clears any settings from the previous domain.
1764                         applyDomainSettings(formattedUrlString, true, false);
1765                     } else {  // `WebView` has loaded a webpage.
1766                         // Set the formatted URL string.  Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly.
1767                         formattedUrlString = mainWebView.getUrl();
1768
1769                         // Only update the URL text box if the user is not typing in it.
1770                         if (!urlTextBox.hasFocus()) {
1771                             // Display the formatted URL text.
1772                             urlTextBox.setText(formattedUrlString);
1773
1774                             // Apply text highlighting to `urlTextBox`.
1775                             highlightUrlText();
1776                         }
1777                     }
1778
1779                     // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
1780                     sslCertificate = mainWebView.getCertificate();
1781
1782                     // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
1783                     if (!gettingIpAddresses) {
1784                         checkPinnedMismatch();
1785                     }
1786                 }
1787
1788                 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.  It is also used to determine when to check for pinned mismatches.
1789                 urlIsLoading = false;
1790             }
1791
1792             // Handle SSL Certificate errors.
1793             @Override
1794             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
1795                 // Get the current website SSL certificate.
1796                 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
1797
1798                 // Extract the individual pieces of information from the current website SSL certificate.
1799                 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
1800                 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
1801                 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
1802                 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
1803                 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
1804                 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
1805                 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
1806                 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
1807
1808                 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
1809                 if (pinnedSslCertificate &&
1810                         currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
1811                         currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
1812                         currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
1813                         currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
1814
1815                     // An SSL certificate is pinned and matches the current domain certificate.  Proceed to the website without displaying an error.
1816                     handler.proceed();
1817                 } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
1818                     // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
1819                     sslErrorHandler = handler;
1820
1821                     // Display the SSL error `AlertDialog`.
1822                     AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
1823                     sslCertificateErrorDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate_error));
1824                 }
1825             }
1826         });
1827
1828         // Get the intent that started the app.
1829         Intent launchingIntent = getIntent();
1830
1831         // Get the information from the intent.
1832         String launchingIntentAction = launchingIntent.getAction();
1833         Uri launchingIntentUriData = launchingIntent.getData();
1834
1835         // If the intent action is a web search, perform the search.
1836         if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1837             // Create an encoded URL string.
1838             String encodedUrlString;
1839
1840             // Sanitize the search input and convert it to a search.
1841             try {
1842                 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
1843             } catch (UnsupportedEncodingException exception) {
1844                 encodedUrlString = "";
1845             }
1846
1847             // Add the base search URL.
1848             formattedUrlString = searchURL + encodedUrlString;
1849         } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
1850             // Set the formatted URL string.
1851             formattedUrlString = launchingIntentUriData.toString();
1852         }
1853
1854         // Load the website if not waiting for Orbot to connect.
1855         if (!waitingForOrbot) {
1856             loadUrl(formattedUrlString);
1857         }
1858     }
1859
1860     @Override
1861     protected void onNewIntent(Intent intent) {
1862         // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
1863         setIntent(intent);
1864
1865         // Get the information from the intent.
1866         String intentAction = intent.getAction();
1867         Uri intentUriData = intent.getData();
1868
1869         // If the intent action is a web search, perform the search.
1870         if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
1871             // Create an encoded URL string.
1872             String encodedUrlString;
1873
1874             // Sanitize the search input and convert it to a search.
1875             try {
1876                 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
1877             } catch (UnsupportedEncodingException exception) {
1878                 encodedUrlString = "";
1879             }
1880
1881             // Add the base search URL.
1882             formattedUrlString = searchURL + encodedUrlString;
1883         } else if (intentUriData != null){  // Check to see if the intent contains a new URL.
1884             // Set the formatted URL string.
1885             formattedUrlString = intentUriData.toString();
1886         }
1887
1888         // Load the URL.
1889         loadUrl(formattedUrlString);
1890
1891         // Close the navigation drawer if it is open.
1892         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1893             drawerLayout.closeDrawer(GravityCompat.START);
1894         }
1895
1896         // Close the bookmarks drawer if it is open.
1897         if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
1898             drawerLayout.closeDrawer(GravityCompat.END);
1899         }
1900
1901         // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
1902         mainWebView.requestFocus();
1903     }
1904
1905     @Override
1906     public void onRestart() {
1907         // Run the default commands.
1908         super.onRestart();
1909
1910         // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
1911         if (proxyThroughOrbot) {
1912             // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
1913             Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
1914
1915             // Send the intent to the Orbot package.
1916             orbotIntent.setPackage("org.torproject.android");
1917
1918             // Make it so.
1919             sendBroadcast(orbotIntent);
1920         }
1921
1922         // Apply the app settings if returning from the Settings activity..
1923         if (reapplyAppSettingsOnRestart) {
1924             // Apply the app settings.
1925             applyAppSettings();
1926
1927             // Reload the webpage if displaying of images has been disabled in the Settings activity.
1928             if (reloadOnRestart) {
1929                 // Reload `mainWebView`.
1930                 mainWebView.reload();
1931
1932                 // Reset `reloadOnRestartBoolean`.
1933                 reloadOnRestart = false;
1934             }
1935
1936             // Reset the return from settings flag.
1937             reapplyAppSettingsOnRestart = false;
1938         }
1939
1940         // Apply the domain settings if returning from the Domains activity.
1941         if (reapplyDomainSettingsOnRestart) {
1942             // Reapply the domain settings.
1943             applyDomainSettings(formattedUrlString, false, true);
1944
1945             // Reset `reapplyDomainSettingsOnRestart`.
1946             reapplyDomainSettingsOnRestart = false;
1947         }
1948
1949         // Load the URL on restart to apply changes to night mode.
1950         if (loadUrlOnRestart) {
1951             // Load the current `formattedUrlString`.
1952             loadUrl(formattedUrlString);
1953
1954             // Reset `loadUrlOnRestart.
1955             loadUrlOnRestart = false;
1956         }
1957
1958         // Update the bookmarks drawer if returning from the Bookmarks activity.
1959         if (restartFromBookmarksActivity) {
1960             // Close the bookmarks drawer.
1961             drawerLayout.closeDrawer(GravityCompat.END);
1962
1963             // Reload the bookmarks drawer.
1964             loadBookmarksFolder();
1965
1966             // Reset `restartFromBookmarksActivity`.
1967             restartFromBookmarksActivity = false;
1968         }
1969
1970         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
1971         updatePrivacyIcons(true);
1972     }
1973
1974     // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
1975     @Override
1976     public void onResume() {
1977         // Run the default commands.
1978         super.onResume();
1979
1980         // Resume JavaScript (if enabled).
1981         mainWebView.resumeTimers();
1982
1983         // Resume `mainWebView`.
1984         mainWebView.onResume();
1985
1986         // Resume the adView for the free flavor.
1987         if (BuildConfig.FLAVOR.contentEquals("free")) {
1988             // Resume the ad.
1989             AdHelper.resumeAd(findViewById(R.id.adview));
1990         }
1991
1992         // Display a message to the user if waiting for Orbot.
1993         if (waitingForOrbot && !orbotStatus.equals("ON")) {
1994             // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
1995             mainWebView.getSettings().setUseWideViewPort(false);
1996
1997             // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
1998             mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
1999         }
2000
2001         if (displayingFullScreenVideo) {
2002             // Remove the translucent overlays.
2003             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2004
2005             // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
2006             drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
2007
2008             /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2009              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2010              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2011              */
2012             rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2013         }
2014     }
2015
2016     @Override
2017     public void onPause() {
2018         // Run the default commands.
2019         super.onPause();
2020
2021         // Pause `mainWebView`.
2022         mainWebView.onPause();
2023
2024         // Stop all JavaScript.
2025         mainWebView.pauseTimers();
2026
2027         // Pause the ad or it will continue to consume resources in the background on the free flavor.
2028         if (BuildConfig.FLAVOR.contentEquals("free")) {
2029             // Pause the ad.
2030             AdHelper.pauseAd(findViewById(R.id.adview));
2031         }
2032     }
2033
2034     @Override
2035     public void onDestroy() {
2036         // Unregister the Orbot status broadcast receiver.
2037         this.unregisterReceiver(orbotStatusBroadcastReceiver);
2038
2039         // Close the bookmarks cursor and database.
2040         bookmarksCursor.close();
2041         bookmarksDatabaseHelper.close();
2042
2043         // Run the default commands.
2044         super.onDestroy();
2045     }
2046
2047     @Override
2048     public boolean onCreateOptionsMenu(Menu menu) {
2049         // Inflate the menu.  This adds items to the action bar if it is present.
2050         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
2051
2052         // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
2053         mainMenu = menu;
2054
2055         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
2056         updatePrivacyIcons(false);
2057
2058         // Get handles for the menu items.
2059         MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2060         MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2061         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2062         MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
2063         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
2064         refreshMenuItem = menu.findItem(R.id.refresh);
2065         blocklistsMenuItem = menu.findItem(R.id.blocklists);
2066         easyListMenuItem = menu.findItem(R.id.easylist);
2067         easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
2068         fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
2069         fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
2070         ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
2071         blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
2072         MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
2073
2074         // Only display third-party cookies if API >= 21
2075         toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
2076
2077         // Only display the form data menu items if the API < 26.
2078         toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2079         clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
2080
2081         // Only show Ad Consent if this is the free flavor.
2082         adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
2083
2084         // Get the shared preference values.
2085         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2086
2087         // Get the status of the additional AppBar icons.
2088         displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
2089
2090         // Set the status of the additional app bar icons.  Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
2091         if (displayAdditionalAppBarIcons) {
2092             toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2093             toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
2094             refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
2095         } else { //Do not display the additional icons.
2096             toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2097             toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2098             refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
2099         }
2100
2101         // Replace Refresh with Stop if a URL is already loading.
2102         if (urlIsLoading) {
2103             // Set the title.
2104             refreshMenuItem.setTitle(R.string.stop);
2105
2106             // If the icon is displayed in the AppBar, set it according to the theme.
2107             if (displayAdditionalAppBarIcons) {
2108                 if (darkTheme) {
2109                     refreshMenuItem.setIcon(R.drawable.close_dark);
2110                 } else {
2111                     refreshMenuItem.setIcon(R.drawable.close_light);
2112                 }
2113             }
2114         }
2115
2116         return true;
2117     }
2118
2119     @Override
2120     public boolean onPrepareOptionsMenu(Menu menu) {
2121         // Get handles for the menu items.
2122         MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
2123         MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
2124         MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
2125         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
2126         MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
2127         MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
2128         MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
2129         MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
2130         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
2131         MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
2132         MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
2133         MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
2134         MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
2135         MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
2136
2137         // Set the text for the domain menu item.
2138         if (domainSettingsApplied) {
2139             addOrEditDomain.setTitle(R.string.edit_domain_settings);
2140         } else {
2141             addOrEditDomain.setTitle(R.string.add_domain_settings);
2142         }
2143
2144         // Set the status of the menu item checkboxes.
2145         toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
2146         toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
2147         toggleDomStorageMenuItem.setChecked(domStorageEnabled);
2148         toggleSaveFormDataMenuItem.setChecked(saveFormDataEnabled);  // Form data can be removed once the minimum API >= 26.
2149         easyListMenuItem.setChecked(easyListEnabled);
2150         easyPrivacyMenuItem.setChecked(easyPrivacyEnabled);
2151         fanboysAnnoyanceListMenuItem.setChecked(fanboysAnnoyanceListEnabled);
2152         fanboysSocialBlockingListMenuItem.setChecked(fanboysSocialBlockingListEnabled);
2153         ultraPrivacyMenuItem.setChecked(ultraPrivacyEnabled);
2154         blockAllThirdPartyRequestsMenuItem.setChecked(blockAllThirdPartyRequests);
2155         swipeToRefreshMenuItem.setChecked(swipeRefreshLayout.isEnabled());
2156         displayImagesMenuItem.setChecked(mainWebView.getSettings().getLoadsImagesAutomatically());
2157         nightModeMenuItem.setChecked(nightMode);
2158         proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
2159
2160         // Enable third-party cookies if first-party cookies are enabled.
2161         toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
2162
2163         // Enable DOM Storage if JavaScript is enabled.
2164         toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
2165
2166         // Enable Clear Cookies if there are any.
2167         clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
2168
2169         // Get a count of the number of files in the Local Storage directory.
2170         File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
2171         int localStorageDirectoryNumberOfFiles = 0;
2172         if (localStorageDirectory.exists()) {
2173             localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
2174         }
2175
2176         // Get a count of the number of files in the IndexedDB directory.
2177         File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
2178         int indexedDBDirectoryNumberOfFiles = 0;
2179         if (indexedDBDirectory.exists()) {
2180             indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
2181         }
2182
2183         // Enable Clear DOM Storage if there is any.
2184         clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
2185
2186         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
2187         if (Build.VERSION.SDK_INT < 26) {
2188             WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
2189             clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
2190         } else {
2191             // Disable clear form data because it is not supported on current version of Android.
2192             clearFormDataMenuItem.setEnabled(false);
2193         }
2194
2195         // Enable Clear Data if any of the submenu items are enabled.
2196         clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
2197
2198         // Disable Fanboy's Social Blocking List if Fanboy's Annoyance List is checked.
2199         fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2200
2201         // Initialize the display names for the blocklists with the number of blocked requests.
2202         blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + blockedRequests);
2203         easyListMenuItem.setTitle(easyListBlockedRequests + " - " + getString(R.string.easylist));
2204         easyPrivacyMenuItem.setTitle(easyPrivacyBlockedRequests + " - " + getString(R.string.easyprivacy));
2205         fanboysAnnoyanceListMenuItem.setTitle(fanboysAnnoyanceListBlockedRequests + " - " + getString(R.string.fanboys_annoyance_list));
2206         fanboysSocialBlockingListMenuItem.setTitle(fanboysSocialBlockingListBlockedRequests + " - " + getString(R.string.fanboys_social_blocking_list));
2207         ultraPrivacyMenuItem.setTitle(ultraPrivacyBlockedRequests + " - " + getString(R.string.ultraprivacy));
2208         blockAllThirdPartyRequestsMenuItem.setTitle(thirdPartyBlockedRequests + " - " + getString(R.string.block_all_third_party_requests));
2209
2210         // Get the current user agent.
2211         String currentUserAgent = mainWebView.getSettings().getUserAgentString();
2212
2213         // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
2214         if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
2215             menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
2216         } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
2217             menu.findItem(R.id.user_agent_webview_default).setChecked(true);
2218         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
2219             menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
2220         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
2221             menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
2222         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
2223             menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
2224         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
2225             menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
2226         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
2227             menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
2228         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
2229             menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
2230         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
2231             menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
2232         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
2233             menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
2234         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
2235             menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
2236         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
2237             menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
2238         } else {  // Custom user agent.
2239             menu.findItem(R.id.user_agent_custom).setChecked(true);
2240         }
2241
2242         // Initialize font size variables.
2243         int fontSize = mainWebView.getSettings().getTextZoom();
2244         String fontSizeTitle;
2245         MenuItem selectedFontSizeMenuItem;
2246
2247         // Prepare the font size title and current size menu item.
2248         switch (fontSize) {
2249             case 25:
2250                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
2251                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
2252                 break;
2253
2254             case 50:
2255                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
2256                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
2257                 break;
2258
2259             case 75:
2260                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
2261                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
2262                 break;
2263
2264             case 100:
2265                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2266                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2267                 break;
2268
2269             case 125:
2270                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
2271                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
2272                 break;
2273
2274             case 150:
2275                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
2276                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
2277                 break;
2278
2279             case 175:
2280                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
2281                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
2282                 break;
2283
2284             case 200:
2285                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
2286                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
2287                 break;
2288
2289             default:
2290                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
2291                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
2292                 break;
2293         }
2294
2295         // Set the font size title and select the current size menu item.
2296         fontSizeMenuItem.setTitle(fontSizeTitle);
2297         selectedFontSizeMenuItem.setChecked(true);
2298
2299         // Run all the other default commands.
2300         super.onPrepareOptionsMenu(menu);
2301
2302         // Display the menu.
2303         return true;
2304     }
2305
2306     @Override
2307     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
2308     @SuppressLint("SetJavaScriptEnabled")
2309     // removeAllCookies is deprecated, but it is required for API < 21.
2310     @SuppressWarnings("deprecation")
2311     public boolean onOptionsItemSelected(MenuItem menuItem) {
2312         // Get the selected menu item ID.
2313         int menuItemId = menuItem.getItemId();
2314
2315         // Run the commands that correlate to the selected menu item.
2316         switch (menuItemId) {
2317             case R.id.toggle_javascript:
2318                 // Switch the status of javaScriptEnabled.
2319                 javaScriptEnabled = !javaScriptEnabled;
2320
2321                 // Apply the new JavaScript status.
2322                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2323
2324                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
2325                 updatePrivacyIcons(true);
2326
2327                 // Display a `Snackbar`.
2328                 if (javaScriptEnabled) {  // JavaScrip is enabled.
2329                     Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
2330                 } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
2331                     Snackbar.make(findViewById(R.id.main_webview), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
2332                 } else {  // Privacy mode.
2333                     Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2334                 }
2335
2336                 // Reload the WebView.
2337                 mainWebView.reload();
2338                 return true;
2339
2340             case R.id.add_or_edit_domain:
2341                 if (domainSettingsApplied) {  // Edit the current domain settings.
2342                     // Reapply the domain settings on returning to `MainWebViewActivity`.
2343                     reapplyDomainSettingsOnRestart = true;
2344                     currentDomainName = "";
2345
2346                     // Create an intent to launch the domains activity.
2347                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
2348
2349                     // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list.
2350                     domainsIntent.putExtra("loadDomain", domainSettingsDatabaseId);
2351                     domainsIntent.putExtra("closeOnBack", true);
2352
2353                     // Make it so.
2354                     startActivity(domainsIntent);
2355                 } else {  // Add a new domain.
2356                     // Apply the new domain settings on returning to `MainWebViewActivity`.
2357                     reapplyDomainSettingsOnRestart = true;
2358                     currentDomainName = "";
2359
2360                     // Get the current domain
2361                     Uri currentUri = Uri.parse(formattedUrlString);
2362                     String currentDomain = currentUri.getHost();
2363
2364                     // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
2365                     DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
2366
2367                     // Create the domain and store the database ID.
2368                     int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
2369
2370                     // Create an intent to launch the domains activity.
2371                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
2372
2373                     // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list.
2374                     domainsIntent.putExtra("loadDomain", newDomainDatabaseId);
2375                     domainsIntent.putExtra("closeOnBack", true);
2376
2377                     // Make it so.
2378                     startActivity(domainsIntent);
2379                 }
2380                 return true;
2381
2382             case R.id.toggle_first_party_cookies:
2383                 // Switch the status of firstPartyCookiesEnabled.
2384                 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
2385
2386                 // Update the menu checkbox.
2387                 menuItem.setChecked(firstPartyCookiesEnabled);
2388
2389                 // Apply the new cookie status.
2390                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
2391
2392                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
2393                 updatePrivacyIcons(true);
2394
2395                 // Display a `Snackbar`.
2396                 if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
2397                     Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2398                 } else if (javaScriptEnabled) {  // JavaScript is still enabled.
2399                     Snackbar.make(findViewById(R.id.main_webview), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2400                 } else {  // Privacy mode.
2401                     Snackbar.make(findViewById(R.id.main_webview), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
2402                 }
2403
2404                 // Reload the WebView.
2405                 mainWebView.reload();
2406                 return true;
2407
2408             case R.id.toggle_third_party_cookies:
2409                 if (Build.VERSION.SDK_INT >= 21) {
2410                     // Switch the status of thirdPartyCookiesEnabled.
2411                     thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
2412
2413                     // Update the menu checkbox.
2414                     menuItem.setChecked(thirdPartyCookiesEnabled);
2415
2416                     // Apply the new cookie status.
2417                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
2418
2419                     // Display a `Snackbar`.
2420                     if (thirdPartyCookiesEnabled) {
2421                         Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
2422                     } else {
2423                         Snackbar.make(findViewById(R.id.main_webview), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
2424                     }
2425
2426                     // Reload the WebView.
2427                     mainWebView.reload();
2428                 } // Else do nothing because SDK < 21.
2429                 return true;
2430
2431             case R.id.toggle_dom_storage:
2432                 // Switch the status of domStorageEnabled.
2433                 domStorageEnabled = !domStorageEnabled;
2434
2435                 // Update the menu checkbox.
2436                 menuItem.setChecked(domStorageEnabled);
2437
2438                 // Apply the new DOM Storage status.
2439                 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
2440
2441                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
2442                 updatePrivacyIcons(true);
2443
2444                 // Display a `Snackbar`.
2445                 if (domStorageEnabled) {
2446                     Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
2447                 } else {
2448                     Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
2449                 }
2450
2451                 // Reload the WebView.
2452                 mainWebView.reload();
2453                 return true;
2454
2455             // Form data can be removed once the minimum API >= 26.
2456             case R.id.toggle_save_form_data:
2457                 // Switch the status of saveFormDataEnabled.
2458                 saveFormDataEnabled = !saveFormDataEnabled;
2459
2460                 // Update the menu checkbox.
2461                 menuItem.setChecked(saveFormDataEnabled);
2462
2463                 // Apply the new form data status.
2464                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
2465
2466                 // Display a `Snackbar`.
2467                 if (saveFormDataEnabled) {
2468                     Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
2469                 } else {
2470                     Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
2471                 }
2472
2473                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
2474                 updatePrivacyIcons(true);
2475
2476                 // Reload the WebView.
2477                 mainWebView.reload();
2478                 return true;
2479
2480             case R.id.clear_cookies:
2481                 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
2482                         .setAction(R.string.undo, v -> {
2483                             // Do nothing because everything will be handled by `onDismissed()` below.
2484                         })
2485                         .addCallback(new Snackbar.Callback() {
2486                             @Override
2487                             public void onDismissed(Snackbar snackbar, int event) {
2488                                 switch (event) {
2489                                     // The user pushed the `Undo` button.
2490                                     case Snackbar.Callback.DISMISS_EVENT_ACTION:
2491                                         // Do nothing.
2492                                         break;
2493
2494                                     // The `Snackbar` was dismissed without the `Undo` button being pushed.
2495                                     default:
2496                                         // `cookieManager.removeAllCookie()` varies by SDK.
2497                                         if (Build.VERSION.SDK_INT < 21) {
2498                                             cookieManager.removeAllCookie();
2499                                         } else {
2500                                             // `null` indicates no callback.
2501                                             cookieManager.removeAllCookies(null);
2502                                         }
2503                                 }
2504                             }
2505                         })
2506                         .show();
2507                 return true;
2508
2509             case R.id.clear_dom_storage:
2510                 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
2511                         .setAction(R.string.undo, v -> {
2512                             // Do nothing because everything will be handled by `onDismissed()` below.
2513                         })
2514                         .addCallback(new Snackbar.Callback() {
2515                             @Override
2516                             public void onDismissed(Snackbar snackbar, int event) {
2517                                 switch (event) {
2518                                     // The user pushed the `Undo` button.
2519                                     case Snackbar.Callback.DISMISS_EVENT_ACTION:
2520                                         // Do nothing.
2521                                         break;
2522
2523                                     // The `Snackbar` was dismissed without the `Undo` button being pushed.
2524                                     default:
2525                                         // Delete the DOM Storage.
2526                                         WebStorage webStorage = WebStorage.getInstance();
2527                                         webStorage.deleteAllData();
2528
2529                                         // Initialize a handler to manually delete the DOM storage files and directories.
2530                                         Handler deleteDomStorageHandler = new Handler();
2531
2532                                         // Setup a runnable to manually delete the DOM storage files and directories.
2533                                         Runnable deleteDomStorageRunnable = () -> {
2534                                             try {
2535                                                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2536                                                 Process deleteLocalStorageProcess = privacyBrowserRuntime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2537
2538                                                 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2539                                                 Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2540                                                 Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2541                                                 Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2542                                                 Process deleteDatabasesProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2543
2544                                                 // Wait for the processes to finish.
2545                                                 deleteLocalStorageProcess.waitFor();
2546                                                 deleteIndexProcess.waitFor();
2547                                                 deleteQuotaManagerProcess.waitFor();
2548                                                 deleteQuotaManagerJournalProcess.waitFor();
2549                                                 deleteDatabasesProcess.waitFor();
2550                                             } catch (Exception exception) {
2551                                                 // Do nothing if an error is thrown.
2552                                             }
2553                                         };
2554
2555                                         // Manually delete the DOM storage files after 200 milliseconds.
2556                                         deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
2557                                 }
2558                             }
2559                         })
2560                         .show();
2561                 return true;
2562
2563             // Form data can be remove once the minimum API >= 26.
2564             case R.id.clear_form_data:
2565                 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
2566                         .setAction(R.string.undo, v -> {
2567                             // Do nothing because everything will be handled by `onDismissed()` below.
2568                         })
2569                         .addCallback(new Snackbar.Callback() {
2570                             @Override
2571                             public void onDismissed(Snackbar snackbar, int event) {
2572                                 switch (event) {
2573                                     // The user pushed the `Undo` button.
2574                                     case Snackbar.Callback.DISMISS_EVENT_ACTION:
2575                                         // Do nothing.
2576                                         break;
2577
2578                                     // The `Snackbar` was dismissed without the `Undo` button being pushed.
2579                                     default:
2580                                         // Delete the form data.
2581                                         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
2582                                         mainWebViewDatabase.clearFormData();
2583                                 }
2584                             }
2585                         })
2586                         .show();
2587                 return true;
2588
2589             case R.id.easylist:
2590                 // Toggle the EasyList status.
2591                 easyListEnabled = !easyListEnabled;
2592
2593                 // Update the menu checkbox.
2594                 menuItem.setChecked(easyListEnabled);
2595
2596                 // Reload the main WebView.
2597                 mainWebView.reload();
2598                 return true;
2599
2600             case R.id.easyprivacy:
2601                 // Toggle the EasyPrivacy status.
2602                 easyPrivacyEnabled = !easyPrivacyEnabled;
2603
2604                 // Update the menu checkbox.
2605                 menuItem.setChecked(easyPrivacyEnabled);
2606
2607                 // Reload the main WebView.
2608                 mainWebView.reload();
2609                 return true;
2610
2611             case R.id.fanboys_annoyance_list:
2612                 // Toggle Fanboy's Annoyance List status.
2613                 fanboysAnnoyanceListEnabled = !fanboysAnnoyanceListEnabled;
2614
2615                 // Update the menu checkbox.
2616                 menuItem.setChecked(fanboysAnnoyanceListEnabled);
2617
2618                 // Update the staus of Fanboy's Social Blocking List.
2619                 MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list);
2620                 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled);
2621
2622                 // Reload the main WebView.
2623                 mainWebView.reload();
2624                 return true;
2625
2626             case R.id.fanboys_social_blocking_list:
2627                 // Toggle Fanboy's Social Blocking List status.
2628                 fanboysSocialBlockingListEnabled = !fanboysSocialBlockingListEnabled;
2629
2630                 // Update the menu checkbox.
2631                 menuItem.setChecked(fanboysSocialBlockingListEnabled);
2632
2633                 // Reload the main WebView.
2634                 mainWebView.reload();
2635                 return true;
2636
2637             case R.id.ultraprivacy:
2638                 // Toggle the UltraPrivacy status.
2639                 ultraPrivacyEnabled = !ultraPrivacyEnabled;
2640
2641                 // Update the menu checkbox.
2642                 menuItem.setChecked(ultraPrivacyEnabled);
2643
2644                 // Reload the main WebView.
2645                 mainWebView.reload();
2646                 return true;
2647
2648             case R.id.block_all_third_party_requests:
2649                 //Toggle the third-party requests blocker status.
2650                 blockAllThirdPartyRequests = !blockAllThirdPartyRequests;
2651
2652                 // Update the menu checkbox.
2653                 menuItem.setChecked(blockAllThirdPartyRequests);
2654
2655                 // Reload the main WebView.
2656                 mainWebView.reload();
2657                 return true;
2658
2659             case R.id.user_agent_privacy_browser:
2660                 // Update the user agent.
2661                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
2662
2663                 // Reload the WebView.
2664                 mainWebView.reload();
2665                 return true;
2666
2667             case R.id.user_agent_webview_default:
2668                 // Update the user agent.
2669                 mainWebView.getSettings().setUserAgentString("");
2670
2671                 // Reload the WebView.
2672                 mainWebView.reload();
2673                 return true;
2674
2675             case R.id.user_agent_firefox_on_android:
2676                 // Update the user agent.
2677                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
2678
2679                 // Reload the WebView.
2680                 mainWebView.reload();
2681                 return true;
2682
2683             case R.id.user_agent_chrome_on_android:
2684                 // Update the user agent.
2685                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
2686
2687                 // Reload the WebView.
2688                 mainWebView.reload();
2689                 return true;
2690
2691             case R.id.user_agent_safari_on_ios:
2692                 // Update the user agent.
2693                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
2694
2695                 // Reload the WebView.
2696                 mainWebView.reload();
2697                 return true;
2698
2699             case R.id.user_agent_firefox_on_linux:
2700                 // Update the user agent.
2701                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
2702
2703                 // Reload the WebView.
2704                 mainWebView.reload();
2705                 return true;
2706
2707             case R.id.user_agent_chromium_on_linux:
2708                 // Update the user agent.
2709                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
2710
2711                 // Reload the WebView.
2712                 mainWebView.reload();
2713                 return true;
2714
2715             case R.id.user_agent_firefox_on_windows:
2716                 // Update the user agent.
2717                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
2718
2719                 // Reload the WebView.
2720                 mainWebView.reload();
2721                 return true;
2722
2723             case R.id.user_agent_chrome_on_windows:
2724                 // Update the user agent.
2725                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
2726
2727                 // Reload the WebView.
2728                 mainWebView.reload();
2729                 return true;
2730
2731             case R.id.user_agent_edge_on_windows:
2732                 // Update the user agent.
2733                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
2734
2735                 // Reload the WebView.
2736                 mainWebView.reload();
2737                 return true;
2738
2739             case R.id.user_agent_internet_explorer_on_windows:
2740                 // Update the user agent.
2741                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
2742
2743                 // Reload the WebView.
2744                 mainWebView.reload();
2745                 return true;
2746
2747             case R.id.user_agent_safari_on_macos:
2748                 // Update the user agent.
2749                 mainWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
2750
2751                 // Reload the WebView.
2752                 mainWebView.reload();
2753                 return true;
2754
2755             case R.id.user_agent_custom:
2756                 // Update the user agent.
2757                 mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
2758
2759                 // Reload the WebView.
2760                 mainWebView.reload();
2761                 return true;
2762
2763             case R.id.font_size_twenty_five_percent:
2764                 mainWebView.getSettings().setTextZoom(25);
2765                 return true;
2766
2767             case R.id.font_size_fifty_percent:
2768                 mainWebView.getSettings().setTextZoom(50);
2769                 return true;
2770
2771             case R.id.font_size_seventy_five_percent:
2772                 mainWebView.getSettings().setTextZoom(75);
2773                 return true;
2774
2775             case R.id.font_size_one_hundred_percent:
2776                 mainWebView.getSettings().setTextZoom(100);
2777                 return true;
2778
2779             case R.id.font_size_one_hundred_twenty_five_percent:
2780                 mainWebView.getSettings().setTextZoom(125);
2781                 return true;
2782
2783             case R.id.font_size_one_hundred_fifty_percent:
2784                 mainWebView.getSettings().setTextZoom(150);
2785                 return true;
2786
2787             case R.id.font_size_one_hundred_seventy_five_percent:
2788                 mainWebView.getSettings().setTextZoom(175);
2789                 return true;
2790
2791             case R.id.font_size_two_hundred_percent:
2792                 mainWebView.getSettings().setTextZoom(200);
2793                 return true;
2794
2795             case R.id.swipe_to_refresh:
2796                 // Toggle swipe to refresh.
2797                 swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
2798                 return true;
2799
2800             case R.id.display_images:
2801                 if (mainWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
2802                     mainWebView.getSettings().setLoadsImagesAutomatically(false);
2803                     mainWebView.reload();
2804                 } else {  // Images are not currently loaded automatically.
2805                     mainWebView.getSettings().setLoadsImagesAutomatically(true);
2806                 }
2807                 return true;
2808
2809             case R.id.night_mode:
2810                 // Toggle night mode.
2811                 nightMode = !nightMode;
2812
2813                 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
2814                 if (nightMode) {  // Night mode is enabled.  Enable JavaScript.
2815                     // Update the global variable.
2816                     javaScriptEnabled = true;
2817                 } else if (domainSettingsApplied) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
2818                     // Get the JavaScript preference that was stored the last time domain settings were loaded.
2819                     javaScriptEnabled = domainSettingsJavaScriptEnabled;
2820                 } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
2821                     // Get a handle for the shared preference.
2822                     SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2823
2824                     // Get the JavaScript preference.
2825                     javaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
2826                 }
2827
2828                 // Apply the JavaScript setting to the WebView.
2829                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
2830
2831                 // Update the privacy icons.
2832                 updatePrivacyIcons(false);
2833
2834                 // Reload the website.
2835                 mainWebView.reload();
2836                 return true;
2837
2838             case R.id.find_on_page:
2839                 // Hide the URL app bar.
2840                 supportAppBar.setVisibility(View.GONE);
2841
2842                 // Show the Find on Page `RelativeLayout`.
2843                 findOnPageLinearLayout.setVisibility(View.VISIBLE);
2844
2845                 // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.
2846                 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
2847                 findOnPageEditText.postDelayed(() -> {
2848                     // Set the focus on `findOnPageEditText`.
2849                     findOnPageEditText.requestFocus();
2850
2851                     // Display the keyboard.  `0` sets no input flags.
2852                     inputMethodManager.showSoftInput(findOnPageEditText, 0);
2853                 }, 200);
2854                 return true;
2855
2856             case R.id.view_source:
2857                 // Launch the View Source activity.
2858                 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
2859                 startActivity(viewSourceIntent);
2860                 return true;
2861
2862             case R.id.share_url:
2863                 // Setup the share string.
2864                 String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
2865
2866                 // Create the share intent.
2867                 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2868                 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2869                 shareIntent.setType("text/plain");
2870
2871                 // Make it so.
2872                 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
2873                 return true;
2874
2875             case R.id.print:
2876                 // Get a `PrintManager` instance.
2877                 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2878
2879                 // Convert `mainWebView` to `printDocumentAdapter`.
2880                 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
2881
2882                 // Remove the lint error below that `printManager` might be `null`.
2883                 assert printManager != null;
2884
2885                 // Print the document.  The print attributes are `null`.
2886                 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2887                 return true;
2888
2889             case R.id.open_with_app:
2890                 openWithApp(formattedUrlString);
2891                 return true;
2892
2893             case R.id.open_with_browser:
2894                 openWithBrowser(formattedUrlString);
2895                 return true;
2896
2897             case R.id.add_to_homescreen:
2898                 // Show the alert dialog.
2899                 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
2900                 createHomeScreenShortcutDialogFragment.show(supportFragmentManager, getString(R.string.create_shortcut));
2901
2902                 //Everything else will be handled by the alert dialog and the associated listener below.
2903                 return true;
2904
2905             case R.id.proxy_through_orbot:
2906                 // Toggle the proxy through Orbot variable.
2907                 proxyThroughOrbot = !proxyThroughOrbot;
2908
2909                 // Apply the proxy through Orbot settings.
2910                 applyProxyThroughOrbot(true);
2911                 return true;
2912
2913             case R.id.refresh:
2914                 if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.