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