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