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