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