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