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