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