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