0fd451611a28312b03479e36194e921e89c08878
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
1 /*
2  * Copyright © 2015-2021 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.annotation.SuppressLint;
25 import android.app.Activity;
26 import android.app.Dialog;
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.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.BitmapFactory;
42 import android.graphics.Typeface;
43 import android.graphics.drawable.BitmapDrawable;
44 import android.graphics.drawable.Drawable;
45 import android.net.Uri;
46 import android.net.http.SslCertificate;
47 import android.net.http.SslError;
48 import android.os.AsyncTask;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Environment;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.preference.PreferenceManager;
55 import android.print.PrintDocumentAdapter;
56 import android.print.PrintManager;
57 import android.provider.DocumentsContract;
58 import android.provider.OpenableColumns;
59 import android.text.Editable;
60 import android.text.Spanned;
61 import android.text.TextWatcher;
62 import android.text.style.ForegroundColorSpan;
63 import android.util.Patterns;
64 import android.util.TypedValue;
65 import android.view.ContextMenu;
66 import android.view.GestureDetector;
67 import android.view.KeyEvent;
68 import android.view.Menu;
69 import android.view.MenuItem;
70 import android.view.MotionEvent;
71 import android.view.View;
72 import android.view.ViewGroup;
73 import android.view.WindowManager;
74 import android.view.inputmethod.InputMethodManager;
75 import android.webkit.CookieManager;
76 import android.webkit.HttpAuthHandler;
77 import android.webkit.SslErrorHandler;
78 import android.webkit.ValueCallback;
79 import android.webkit.WebBackForwardList;
80 import android.webkit.WebChromeClient;
81 import android.webkit.WebResourceResponse;
82 import android.webkit.WebSettings;
83 import android.webkit.WebStorage;
84 import android.webkit.WebView;
85 import android.webkit.WebViewClient;
86 import android.webkit.WebViewDatabase;
87 import android.widget.ArrayAdapter;
88 import android.widget.CheckBox;
89 import android.widget.CursorAdapter;
90 import android.widget.EditText;
91 import android.widget.FrameLayout;
92 import android.widget.ImageView;
93 import android.widget.LinearLayout;
94 import android.widget.ListView;
95 import android.widget.ProgressBar;
96 import android.widget.RadioButton;
97 import android.widget.RelativeLayout;
98 import android.widget.TextView;
99
100 import androidx.activity.result.ActivityResultCallback;
101 import androidx.activity.result.ActivityResultLauncher;
102 import androidx.activity.result.contract.ActivityResultContracts;
103 import androidx.annotation.NonNull;
104 import androidx.appcompat.app.ActionBar;
105 import androidx.appcompat.app.ActionBarDrawerToggle;
106 import androidx.appcompat.app.AppCompatActivity;
107 import androidx.appcompat.app.AppCompatDelegate;
108 import androidx.appcompat.widget.Toolbar;
109 import androidx.coordinatorlayout.widget.CoordinatorLayout;
110 import androidx.core.content.res.ResourcesCompat;
111 import androidx.core.view.GravityCompat;
112 import androidx.drawerlayout.widget.DrawerLayout;
113 import androidx.fragment.app.DialogFragment;
114 import androidx.fragment.app.Fragment;
115 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
116 import androidx.viewpager.widget.ViewPager;
117 import androidx.webkit.WebSettingsCompat;
118 import androidx.webkit.WebViewFeature;
119
120 import com.google.android.material.appbar.AppBarLayout;
121 import com.google.android.material.floatingactionbutton.FloatingActionButton;
122 import com.google.android.material.navigation.NavigationView;
123 import com.google.android.material.snackbar.Snackbar;
124 import com.google.android.material.tabs.TabLayout;
125
126 import com.stoutner.privacybrowser.BuildConfig;
127 import com.stoutner.privacybrowser.R;
128 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
129 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
130 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
131 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
132 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
133 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
134 import com.stoutner.privacybrowser.dataclasses.PendingDialog;
135 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
136 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
137 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
138 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
139 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
140 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
141 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
142 import com.stoutner.privacybrowser.dialogs.OpenDialog;
143 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
144 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
145 import com.stoutner.privacybrowser.dialogs.SaveDialog;
146 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
147 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
148 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
149 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
150 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
151 import com.stoutner.privacybrowser.helpers.AdHelper;
152 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
153 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
154 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
155 import com.stoutner.privacybrowser.helpers.ProxyHelper;
156 import com.stoutner.privacybrowser.views.NestedScrollWebView;
157
158 import java.io.ByteArrayInputStream;
159 import java.io.ByteArrayOutputStream;
160 import java.io.File;
161 import java.io.FileInputStream;
162 import java.io.FileOutputStream;
163 import java.io.IOException;
164 import java.io.InputStream;
165 import java.io.OutputStream;
166 import java.io.UnsupportedEncodingException;
167
168 import java.net.MalformedURLException;
169 import java.net.URL;
170 import java.net.URLDecoder;
171 import java.net.URLEncoder;
172
173 import java.text.NumberFormat;
174
175 import java.util.ArrayList;
176 import java.util.Calendar;
177 import java.util.Date;
178 import java.util.HashMap;
179 import java.util.HashSet;
180 import java.util.List;
181 import java.util.Map;
182 import java.util.Objects;
183 import java.util.Set;
184 import java.util.concurrent.ExecutorService;
185 import java.util.concurrent.Executors;
186
187 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
188         EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
189         PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener,
190         WebViewTabFragment.NewTabListener {
191
192     // The executor service handles background tasks.  It is accessed from `ViewSourceActivity`.
193     public static ExecutorService executorService = Executors.newFixedThreadPool(4);
194
195     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
196     public static String orbotStatus = "unknown";
197
198     // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`.  It is also used in `onCreate()`, `onResume()`, and `addTab()`.
199     public static WebViewPagerAdapter webViewPagerAdapter;
200
201     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
202     public static boolean restartFromBookmarksActivity;
203
204     // Define the public static variables.
205     public static ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
206
207     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
208     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
209     public static String currentBookmarksFolder;
210
211     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
212     public final static int UNRECOGNIZED_USER_AGENT = -1;
213     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
214     public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
215     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
216     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
217     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
218
219     // Define the start activity for result request codes.  The public static entry is accessed from `OpenDialog()`.
220     private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
221     public final static int BROWSE_OPEN_REQUEST_CODE = 1;
222
223     // The proxy mode is public static so it can be accessed from `ProxyHelper()`.
224     // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
225     // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
226     public static String proxyMode = ProxyHelper.NONE;
227
228     // Define the saved instance state constants.
229     private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
230     private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
231     private final String SAVED_TAB_POSITION = "saved_tab_position";
232     private final String PROXY_MODE = "proxy_mode";
233
234     // Define the saved instance state variables.
235     private ArrayList<Bundle> savedStateArrayList;
236     private ArrayList<Bundle> savedNestedScrollWebViewStateArrayList;
237     private int savedTabPosition;
238     private String savedProxyMode;
239
240     // Define the class variables.
241     @SuppressWarnings("rawtypes")
242     AsyncTask populateBlocklists;
243
244     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
245     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
246     private NestedScrollWebView currentWebView;
247
248     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
249     private final Map<String, String> customHeaders = new HashMap<>();
250
251     // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
252     private String searchURL;
253
254     // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
255     private ArrayList<List<String[]>> easyList;
256     private ArrayList<List<String[]>> easyPrivacy;
257     private ArrayList<List<String[]>> fanboysAnnoyanceList;
258     private ArrayList<List<String[]>> fanboysSocialList;
259     private ArrayList<List<String[]>> ultraList;
260     private ArrayList<List<String[]>> ultraPrivacy;
261
262     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
263     private ActionBarDrawerToggle actionBarDrawerToggle;
264
265     // The color spans are used in `onCreate()` and `highlightUrlText()`.
266     private ForegroundColorSpan redColorSpan;
267     private ForegroundColorSpan initialGrayColorSpan;
268     private ForegroundColorSpan finalGrayColorSpan;
269
270     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
271     // and `loadBookmarksFolder()`.
272     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
273
274     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
275     private Cursor bookmarksCursor;
276
277     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
278     private CursorAdapter bookmarksCursorAdapter;
279
280     // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
281     private String oldFolderNameString;
282
283     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
284     private ValueCallback<Uri[]> fileChooserCallback;
285
286     // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
287     private int appBarHeight;
288     private int defaultProgressViewStartOffset;
289     private int defaultProgressViewEndOffset;
290
291     // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
292     private boolean sanitizeGoogleAnalytics;
293     private boolean sanitizeFacebookClickIds;
294     private boolean sanitizeTwitterAmpRedirects;
295
296     // Declare the class variables
297     private BroadcastReceiver orbotStatusBroadcastReceiver;
298     private String webViewDefaultUserAgent;
299     private boolean incognitoModeEnabled;
300     private boolean fullScreenBrowsingModeEnabled;
301     private boolean inFullScreenBrowsingMode;
302     private boolean downloadWithExternalApp;
303     private boolean hideAppBar;
304     private boolean scrollAppBar;
305     private boolean bottomAppBar;
306     private boolean loadingNewIntent;
307     private boolean reapplyDomainSettingsOnRestart;
308     private boolean reapplyAppSettingsOnRestart;
309     private boolean displayingFullScreenVideo;
310     private boolean waitingForProxy;
311
312     // Define the class variables.
313     private long lastScrollUpdate = 0;
314     private String saveUrlString = "";
315
316     // Declare the class views.
317     private FrameLayout rootFrameLayout;
318     private DrawerLayout drawerLayout;
319     private RelativeLayout mainContentRelativeLayout;
320     private AppBarLayout appBarLayout;
321     private Toolbar toolbar;
322     private RelativeLayout urlRelativeLayout;
323     private EditText urlEditText;
324     private ActionBar actionBar;
325     private LinearLayout findOnPageLinearLayout;
326     private LinearLayout tabsLinearLayout;
327     private TabLayout tabLayout;
328     private SwipeRefreshLayout swipeRefreshLayout;
329     private ViewPager webViewPager;
330     private FrameLayout fullScreenVideoFrameLayout;
331
332     // Declare the class menus.
333     private Menu optionsMenu;
334
335     // Declare the class menu items.
336     private MenuItem navigationBackMenuItem;
337     private MenuItem navigationForwardMenuItem;
338     private MenuItem navigationHistoryMenuItem;
339     private MenuItem navigationRequestsMenuItem;
340     private MenuItem optionsPrivacyMenuItem;
341     private MenuItem optionsRefreshMenuItem;
342     private MenuItem optionsCookiesMenuItem;
343     private MenuItem optionsDomStorageMenuItem;
344     private MenuItem optionsSaveFormDataMenuItem;
345     private MenuItem optionsClearDataMenuItem;
346     private MenuItem optionsClearCookiesMenuItem;
347     private MenuItem optionsClearDomStorageMenuItem;
348     private MenuItem optionsClearFormDataMenuItem;
349     private MenuItem optionsBlocklistsMenuItem;
350     private MenuItem optionsEasyListMenuItem;
351     private MenuItem optionsEasyPrivacyMenuItem;
352     private MenuItem optionsFanboysAnnoyanceListMenuItem;
353     private MenuItem optionsFanboysSocialBlockingListMenuItem;
354     private MenuItem optionsUltraListMenuItem;
355     private MenuItem optionsUltraPrivacyMenuItem;
356     private MenuItem optionsBlockAllThirdPartyRequestsMenuItem;
357     private MenuItem optionsProxyMenuItem;
358     private MenuItem optionsProxyNoneMenuItem;
359     private MenuItem optionsProxyTorMenuItem;
360     private MenuItem optionsProxyI2pMenuItem;
361     private MenuItem optionsProxyCustomMenuItem;
362     private MenuItem optionsUserAgentMenuItem;
363     private MenuItem optionsUserAgentPrivacyBrowserMenuItem;
364     private MenuItem optionsUserAgentWebViewDefaultMenuItem;
365     private MenuItem optionsUserAgentFirefoxOnAndroidMenuItem;
366     private MenuItem optionsUserAgentChromeOnAndroidMenuItem;
367     private MenuItem optionsUserAgentSafariOnIosMenuItem;
368     private MenuItem optionsUserAgentFirefoxOnLinuxMenuItem;
369     private MenuItem optionsUserAgentChromiumOnLinuxMenuItem;
370     private MenuItem optionsUserAgentFirefoxOnWindowsMenuItem;
371     private MenuItem optionsUserAgentChromeOnWindowsMenuItem;
372     private MenuItem optionsUserAgentEdgeOnWindowsMenuItem;
373     private MenuItem optionsUserAgentInternetExplorerOnWindowsMenuItem;
374     private MenuItem optionsUserAgentSafariOnMacosMenuItem;
375     private MenuItem optionsUserAgentCustomMenuItem;
376     private MenuItem optionsSwipeToRefreshMenuItem;
377     private MenuItem optionsWideViewportMenuItem;
378     private MenuItem optionsDisplayImagesMenuItem;
379     private MenuItem optionsDarkWebViewMenuItem;
380     private MenuItem optionsFontSizeMenuItem;
381     private MenuItem optionsAddOrEditDomainMenuItem;
382
383     // This variable won't be needed once the class is migrated to Kotlin, as can be seen in LogcatActivity or AboutVersionFragment.
384     private Activity resultLauncherActivityHandle;
385
386     // Define the save URL activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
387     private final ActivityResultLauncher<String> saveUrlActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(),
388             new ActivityResultCallback<Uri>() {
389                 @Override
390                 public void onActivityResult(Uri fileUri) {
391                     // Only save the URL if the file URI is not null, which happens if the user exited the file picker by pressing back.
392                     if (fileUri != null) {
393                         new SaveUrl(getApplicationContext(), resultLauncherActivityHandle, fileUri, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(saveUrlString);
394                     }
395
396                     // Reset the save URL string.
397                     saveUrlString = "";
398                 }
399             });
400
401     // Define the save webpage archive activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
402     private final ActivityResultLauncher<String> saveWebpageArchiveActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(),
403             new ActivityResultCallback<Uri>() {
404                 @Override
405                 public void onActivityResult(Uri fileUri) {
406                     // Only save the webpage archive if the file URI is not null, which happens if the user exited the file picker by pressing back.
407                     if (fileUri != null) {
408                         try {
409                             // Create a temporary MHT file.
410                             File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
411
412                             // Save the temporary MHT file.
413                             currentWebView.saveWebArchive(temporaryMhtFile.toString(), false, callbackValue -> {
414                                 if (callbackValue != null) {  // The temporary MHT file was saved successfully.
415                                     try {
416                                         // Create a temporary MHT file input stream.
417                                         FileInputStream temporaryMhtFileInputStream = new FileInputStream(temporaryMhtFile);
418
419                                         // Get an output stream for the save webpage file path.
420                                         OutputStream mhtOutputStream = getContentResolver().openOutputStream(fileUri);
421
422                                         // Create a transfer byte array.
423                                         byte[] transferByteArray = new byte[1024];
424
425                                         // Create an integer to track the number of bytes read.
426                                         int bytesRead;
427
428                                         // Copy the temporary MHT file input stream to the MHT output stream.
429                                         while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) {
430                                             mhtOutputStream.write(transferByteArray, 0, bytesRead);
431                                         }
432
433                                         // Close the streams.
434                                         mhtOutputStream.close();
435                                         temporaryMhtFileInputStream.close();
436
437                                         // Initialize the file name string from the file URI last path segment.
438                                         String fileNameString = fileUri.getLastPathSegment();
439
440                                         // Query the exact file name if the API >= 26.
441                                         if (Build.VERSION.SDK_INT >= 26) {
442                                             // Get a cursor from the content resolver.
443                                             Cursor contentResolverCursor = resultLauncherActivityHandle.getContentResolver().query(fileUri, null, null, null);
444
445                                             // Move to the fist row.
446                                             contentResolverCursor.moveToFirst();
447
448                                             // Get the file name from the cursor.
449                                             fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
450
451                                             // Close the cursor.
452                                             contentResolverCursor.close();
453                                         }
454
455                                         // Display a snackbar.
456                                         Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + fileNameString, Snackbar.LENGTH_SHORT).show();
457                                     } catch (Exception exception) {
458                                         // Display a snackbar with the exception.
459                                         Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
460                                     } finally {
461                                         // Delete the temporary MHT file.
462                                         //noinspection ResultOfMethodCallIgnored
463                                         temporaryMhtFile.delete();
464                                     }
465                                 } else {  // There was an unspecified error while saving the temporary MHT file.
466                                     // Display an error snackbar.
467                                     Snackbar.make(currentWebView, getString(R.string.error_saving_file), Snackbar.LENGTH_INDEFINITE).show();
468                                 }
469                             });
470                         } catch (IOException ioException) {
471                             // Display a snackbar with the IO exception.
472                             Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show();
473                         }
474                     }
475                 }
476             });
477
478     // Define the save webpage image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
479     private final ActivityResultLauncher<String> saveWebpageImageActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument(),
480             new ActivityResultCallback<Uri>() {
481                 @Override
482                 public void onActivityResult(Uri fileUri) {
483                     // Only save the webpage image if the file URI is not null, which happens if the user exited the file picker by pressing back.
484                     if (fileUri != null) {
485                         // Save the webpage image.
486                         new SaveWebpageImage(resultLauncherActivityHandle, fileUri, currentWebView).execute();
487                     }
488                 }
489             });
490
491     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with WebView.
492     @SuppressLint("ClickableViewAccessibility")
493     @Override
494     protected void onCreate(Bundle savedInstanceState) {
495         // Run the default commands.
496         super.onCreate(savedInstanceState);
497
498         // Populate the result launcher activity.  This will no longer be needed once the activity has transitioned to Kotlin.
499         resultLauncherActivityHandle = this;
500
501         // Check to see if the activity has been restarted.
502         if (savedInstanceState != null) {
503             // Store the saved instance state variables.
504             savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
505             savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
506             savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
507             savedProxyMode = savedInstanceState.getString(PROXY_MODE);
508         }
509
510         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
511         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
512
513         // Get a handle for the shared preferences.
514         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
515
516         // Get the preferences.
517         String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
518         boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
519         bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
520
521         // Get the theme entry values string array.
522         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
523
524         // Set the app theme according to the preference.  A switch statement cannot be used because the theme entry values string array is not a compile time constant.
525         if (appTheme.equals(appThemeEntryValuesStringArray[1])) {  // The light theme is selected.
526             // Apply the light theme.
527             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
528         } else if (appTheme.equals(appThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
529             // Apply the dark theme.
530             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
531         } else {  // The system default theme is selected.
532             if (Build.VERSION.SDK_INT >= 28) {  // The system default theme is supported.
533                 // Follow the system default theme.
534                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
535             } else {  // The system default theme is not supported.
536                 // Follow the battery saver mode.
537                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
538             }
539         }
540
541         // Disable screenshots if not allowed.
542         if (!allowScreenshots) {
543             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
544         }
545
546         // Enable the drawing of the entire webpage.  This makes it possible to save a website image.  This must be done before anything else happens with the WebView.
547         if (Build.VERSION.SDK_INT >= 21) {
548             WebView.enableSlowWholeDocumentDraw();
549         }
550
551         // Set the theme.
552         setTheme(R.style.PrivacyBrowser);
553
554         // Set the content view.
555         if (bottomAppBar) {
556             setContentView(R.layout.main_framelayout_bottom_appbar);
557         } else {
558             setContentView(R.layout.main_framelayout_top_appbar);
559         }
560
561         // Get handles for the views.
562         rootFrameLayout = findViewById(R.id.root_framelayout);
563         drawerLayout = findViewById(R.id.drawerlayout);
564         mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
565         appBarLayout = findViewById(R.id.appbar_layout);
566         toolbar = findViewById(R.id.toolbar);
567         findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
568         tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
569         tabLayout = findViewById(R.id.tablayout);
570         swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
571         webViewPager = findViewById(R.id.webviewpager);
572         NavigationView navigationView = findViewById(R.id.navigationview);
573         fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
574
575         // Get a handle for the navigation menu.
576         Menu navigationMenu = navigationView.getMenu();
577
578         // Get handles for the navigation menu items.
579         navigationBackMenuItem = navigationMenu.findItem(R.id.back);
580         navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
581         navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
582         navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
583
584         // Listen for touches on the navigation menu.
585         navigationView.setNavigationItemSelectedListener(this);
586
587         // Get a handle for the app compat delegate.
588         AppCompatDelegate appCompatDelegate = getDelegate();
589
590         // Set the support action bar.
591         appCompatDelegate.setSupportActionBar(toolbar);
592
593         // Get a handle for the action bar.
594         actionBar = appCompatDelegate.getSupportActionBar();
595
596         // Remove the incorrect lint warning below that the action bar might be null.
597         assert actionBar != null;
598
599         // Add the custom layout, which shows the URL text bar.
600         actionBar.setCustomView(R.layout.url_app_bar);
601         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
602
603         // Get handles for the views in the URL app bar.
604         urlRelativeLayout = findViewById(R.id.url_relativelayout);
605         urlEditText = findViewById(R.id.url_edittext);
606
607         // Create the hamburger icon at the start of the AppBar.
608         actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
609
610         // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
611         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
612
613         // Initialize the web view pager adapter.
614         webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
615
616         // Set the pager adapter on the web view pager.
617         webViewPager.setAdapter(webViewPagerAdapter);
618
619         // Store up to 100 tabs in memory.
620         webViewPager.setOffscreenPageLimit(100);
621
622         // Initialize the app.
623         initializeApp();
624
625         // Apply the app settings from the shared preferences.
626         applyAppSettings();
627
628         // Populate the blocklists.
629         populateBlocklists = new PopulateBlocklists(this, this).execute();
630     }
631
632     @Override
633     protected void onNewIntent(Intent intent) {
634         // Run the default commands.
635         super.onNewIntent(intent);
636
637         // Replace the intent that started the app with this one.
638         setIntent(intent);
639
640         // Check to see if the app is being restarted from a saved state.
641         if (savedStateArrayList == null || savedStateArrayList.size() == 0) {  // The activity is not being restarted from a saved state.
642             // Get the information from the intent.
643             String intentAction = intent.getAction();
644             Uri intentUriData = intent.getData();
645             String intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT);
646
647             // Determine if this is a web search.
648             boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
649
650             // Only process the URI if it contains data or it is a web search.  If the user pressed the desktop icon after the app was already running the URI will be null.
651             if (intentUriData != null || intentStringExtra != null || isWebSearch) {
652                 // Exit the full screen video if it is displayed.
653                 if (displayingFullScreenVideo) {
654                     // Exit full screen video mode.
655                     exitFullScreenVideo();
656
657                     // Reload the current WebView.  Otherwise, it can display entirely black.
658                     currentWebView.reload();
659                 }
660
661                 // Get the shared preferences.
662                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
663
664                 // Create a URL string.
665                 String url;
666
667                 // If the intent action is a web search, perform the search.
668                 if (isWebSearch) {  // The intent is a web search.
669                     // Create an encoded URL string.
670                     String encodedUrlString;
671
672                     // Sanitize the search input and convert it to a search.
673                     try {
674                         encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
675                     } catch (UnsupportedEncodingException exception) {
676                         encodedUrlString = "";
677                     }
678
679                     // Add the base search URL.
680                     url = searchURL + encodedUrlString;
681                 } else if (intentUriData != null) {  // The intent contains a URL formatted as a URI.
682                     // Set the intent data as the URL.
683                     url = intentUriData.toString();
684                 } else {  // The intent contains a string, which might be a URL.
685                     // Set the intent string as the URL.
686                     url = intentStringExtra;
687                 }
688
689                 // Add a new tab if specified in the preferences.
690                 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
691                     // Set the loading new intent flag.
692                     loadingNewIntent = true;
693
694                     // Add a new tab.
695                     addNewTab(url, true);
696                 } else {  // Load the URL in the current tab.
697                     // Make it so.
698                     loadUrl(currentWebView, url);
699                 }
700
701                 // Close the navigation drawer if it is open.
702                 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
703                     drawerLayout.closeDrawer(GravityCompat.START);
704                 }
705
706                 // Close the bookmarks drawer if it is open.
707                 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
708                     drawerLayout.closeDrawer(GravityCompat.END);
709                 }
710             }
711         }
712     }
713
714     @Override
715     public void onRestart() {
716         // Run the default commands.
717         super.onRestart();
718
719         // Apply the app settings if returning from the Settings activity.
720         if (reapplyAppSettingsOnRestart) {
721             // Reset the reapply app settings on restart tracker.
722             reapplyAppSettingsOnRestart = false;
723
724             // Apply the app settings.
725             applyAppSettings();
726         }
727
728         // Apply the domain settings if returning from the settings or domains activity.
729         if (reapplyDomainSettingsOnRestart) {
730             // Reset the reapply domain settings on restart tracker.
731             reapplyDomainSettingsOnRestart = false;
732
733             // Reapply the domain settings for each tab.
734             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
735                 // Get the WebView tab fragment.
736                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
737
738                 // Get the fragment view.
739                 View fragmentView = webViewTabFragment.getView();
740
741                 // Only reload the WebViews if they exist.
742                 if (fragmentView != null) {
743                     // Get the nested scroll WebView from the tab fragment.
744                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
745
746                     // Reset the current domain name so the domain settings will be reapplied.
747                     nestedScrollWebView.setCurrentDomainName("");
748
749                     // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
750                     if (nestedScrollWebView.getUrl() != null) {
751                         applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true, false);
752                     }
753                 }
754             }
755         }
756
757         // Update the bookmarks drawer if returning from the Bookmarks activity.
758         if (restartFromBookmarksActivity) {
759             // Close the bookmarks drawer.
760             drawerLayout.closeDrawer(GravityCompat.END);
761
762             // Reload the bookmarks drawer.
763             loadBookmarksFolder();
764
765             // Reset `restartFromBookmarksActivity`.
766             restartFromBookmarksActivity = false;
767         }
768
769         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
770         updatePrivacyIcons(true);
771     }
772
773     // `onStart()` runs after `onCreate()` or `onRestart()`.  This is used instead of `onResume()` so the commands aren't called every time the screen is partially hidden.
774     @Override
775     public void onStart() {
776         // Run the default commands.
777         super.onStart();
778
779         // Resume any WebViews.
780         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
781             // Get the WebView tab fragment.
782             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
783
784             // Get the fragment view.
785             View fragmentView = webViewTabFragment.getView();
786
787             // Only resume the WebViews if they exist (they won't when the app is first created).
788             if (fragmentView != null) {
789                 // Get the nested scroll WebView from the tab fragment.
790                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
791
792                 // Resume the nested scroll WebView.
793                 nestedScrollWebView.onResume();
794             }
795         }
796
797         // Resume the nested scroll WebView JavaScript timers.  This is a global command that resumes JavaScript timers on all WebViews.
798         if (currentWebView != null) {
799             currentWebView.resumeTimers();
800         }
801
802         // Reapply the proxy settings if the system is using a proxy.  This redisplays the appropriate alert dialog.
803         if (!proxyMode.equals(ProxyHelper.NONE)) {
804             applyProxy(false);
805         }
806
807         // Reapply any system UI flags and the ad in the free flavor.
808         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {  // The system is displaying a website or a video in full screen mode.
809             /* Hide the system bars.
810              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
811              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
812              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
813              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
814              */
815             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
816                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
817         } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // The system in not in full screen mode.
818             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
819             View adView = findViewById(R.id.adview);
820
821             // Resume the ad.
822             AdHelper.resumeAd(adView);
823         }
824
825         // Show any pending dialogs.
826         for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
827             // Get the pending dialog from the array list.
828             PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
829
830             // Show the pending dialog.
831             pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
832         }
833
834         // Clear the pending dialogs array list.
835         pendingDialogsArrayList.clear();
836     }
837
838     // `onStop()` runs after `onPause()`.  It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
839     @Override
840     public void onStop() {
841         // Run the default commands.
842         super.onStop();
843
844         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
845             // Get the WebView tab fragment.
846             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
847
848             // Get the fragment view.
849             View fragmentView = webViewTabFragment.getView();
850
851             // Only pause the WebViews if they exist (they won't when the app is first created).
852             if (fragmentView != null) {
853                 // Get the nested scroll WebView from the tab fragment.
854                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
855
856                 // Pause the nested scroll WebView.
857                 nestedScrollWebView.onPause();
858             }
859         }
860
861         // Pause the WebView JavaScript timers.  This is a global command that pauses JavaScript on all WebViews.
862         if (currentWebView != null) {
863             currentWebView.pauseTimers();
864         }
865
866         // Pause the ad or it will continue to consume resources in the background on the free flavor.
867         if (BuildConfig.FLAVOR.contentEquals("free")) {
868             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
869             View adView = findViewById(R.id.adview);
870
871             // Pause the ad.
872             AdHelper.pauseAd(adView);
873         }
874     }
875
876     @Override
877     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
878         // Run the default commands.
879         super.onSaveInstanceState(savedInstanceState);
880
881         // Create the saved state array lists.
882         ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
883         ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
884
885         // Get the URLs from each tab.
886         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
887             // Get the WebView tab fragment.
888             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
889
890             // Get the fragment view.
891             View fragmentView = webViewTabFragment.getView();
892
893             if (fragmentView != null) {
894                 // Get the nested scroll WebView from the tab fragment.
895                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
896
897                 // Create saved state bundle.
898                 Bundle savedStateBundle = new Bundle();
899
900                 // Get the current states.
901                 nestedScrollWebView.saveState(savedStateBundle);
902                 Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
903
904                 // Store the saved states in the array lists.
905                 savedStateArrayList.add(savedStateBundle);
906                 savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
907             }
908         }
909
910         // Get the current tab position.
911         int currentTabPosition = tabLayout.getSelectedTabPosition();
912
913         // Store the saved states in the bundle.
914         savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
915         savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
916         savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
917         savedInstanceState.putString(PROXY_MODE, proxyMode);
918     }
919
920     @Override
921     public void onDestroy() {
922         // Unregister the orbot status broadcast receiver if it exists.
923         if (orbotStatusBroadcastReceiver != null) {
924             this.unregisterReceiver(orbotStatusBroadcastReceiver);
925         }
926
927         // Close the bookmarks cursor if it exists.
928         if (bookmarksCursor != null) {
929             bookmarksCursor.close();
930         }
931
932         // Close the bookmarks database if it exists.
933         if (bookmarksDatabaseHelper != null) {
934             bookmarksDatabaseHelper.close();
935         }
936
937         // Stop populating the blocklists if the AsyncTask is running in the background.
938         if (populateBlocklists != null) {
939             populateBlocklists.cancel(true);
940         }
941
942         // Run the default commands.
943         super.onDestroy();
944     }
945
946     @Override
947     public boolean onCreateOptionsMenu(Menu menu) {
948         // Inflate the menu.  This adds items to the action bar if it is present.
949         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
950
951         // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
952         optionsMenu = menu;
953
954         // Get handles for the class menu items.
955         optionsPrivacyMenuItem = menu.findItem(R.id.javascript);
956         optionsRefreshMenuItem = menu.findItem(R.id.refresh);
957         optionsCookiesMenuItem = menu.findItem(R.id.cookies);
958         optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage);
959         optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data);  // Form data can be removed once the minimum API >= 26.
960         optionsClearDataMenuItem = menu.findItem(R.id.clear_data);
961         optionsClearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
962         optionsClearDomStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
963         optionsClearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
964         optionsBlocklistsMenuItem = menu.findItem(R.id.blocklists);
965         optionsEasyListMenuItem = menu.findItem(R.id.easylist);
966         optionsEasyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
967         optionsFanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
968         optionsFanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
969         optionsUltraListMenuItem = menu.findItem(R.id.ultralist);
970         optionsUltraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
971         optionsBlockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
972         optionsProxyMenuItem = menu.findItem(R.id.proxy);
973         optionsProxyNoneMenuItem = menu.findItem(R.id.proxy_none);
974         optionsProxyTorMenuItem = menu.findItem(R.id.proxy_tor);
975         optionsProxyI2pMenuItem = menu.findItem(R.id.proxy_i2p);
976         optionsProxyCustomMenuItem = menu.findItem(R.id.proxy_custom);
977         optionsUserAgentMenuItem = menu.findItem(R.id.user_agent);
978         optionsUserAgentPrivacyBrowserMenuItem = menu.findItem(R.id.user_agent_privacy_browser);
979         optionsUserAgentWebViewDefaultMenuItem = menu.findItem(R.id.user_agent_webview_default);
980         optionsUserAgentFirefoxOnAndroidMenuItem = menu.findItem(R.id.user_agent_firefox_on_android);
981         optionsUserAgentChromeOnAndroidMenuItem = menu.findItem(R.id.user_agent_chrome_on_android);
982         optionsUserAgentSafariOnIosMenuItem = menu.findItem(R.id.user_agent_safari_on_ios);
983         optionsUserAgentFirefoxOnLinuxMenuItem = menu.findItem(R.id.user_agent_firefox_on_linux);
984         optionsUserAgentChromiumOnLinuxMenuItem = menu.findItem(R.id.user_agent_chromium_on_linux);
985         optionsUserAgentFirefoxOnWindowsMenuItem = menu.findItem(R.id.user_agent_firefox_on_windows);
986         optionsUserAgentChromeOnWindowsMenuItem = menu.findItem(R.id.user_agent_chrome_on_windows);
987         optionsUserAgentEdgeOnWindowsMenuItem = menu.findItem(R.id.user_agent_edge_on_windows);
988         optionsUserAgentInternetExplorerOnWindowsMenuItem = menu.findItem(R.id.user_agent_internet_explorer_on_windows);
989         optionsUserAgentSafariOnMacosMenuItem = menu.findItem(R.id.user_agent_safari_on_macos);
990         optionsUserAgentCustomMenuItem = menu.findItem(R.id.user_agent_custom);
991         optionsSwipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
992         optionsWideViewportMenuItem = menu.findItem(R.id.wide_viewport);
993         optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images);
994         optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview);
995         optionsFontSizeMenuItem = menu.findItem(R.id.font_size);
996         optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain);
997
998         // Get handles for the method menu items.
999         MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks);
1000         MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
1001
1002         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
1003         updatePrivacyIcons(false);
1004
1005         // Only display the form data menu items if the API < 26.
1006         optionsSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
1007         optionsClearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
1008
1009         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
1010         optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
1011
1012         // Only display the dark WebView menu item if API >= 21.
1013         optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
1014
1015         // Only show Ad Consent if this is the free flavor.
1016         adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
1017
1018         // Get the shared preferences.
1019         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1020
1021         // Get the dark theme and app bar preferences.
1022         boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
1023
1024         // 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.
1025         if (displayAdditionalAppBarIcons) {
1026             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
1027             bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1028             optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1029         } else { //Do not display the additional icons.
1030             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1031             bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1032             optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1033         }
1034
1035         // Replace Refresh with Stop if a URL is already loading.
1036         if (currentWebView != null && currentWebView.getProgress() != 100) {
1037             // Set the title.
1038             optionsRefreshMenuItem.setTitle(R.string.stop);
1039
1040             // Set the icon if it is displayed in the app bar.
1041             if (displayAdditionalAppBarIcons) {
1042                 // Get the current theme status.
1043                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
1044
1045                 // Set the icon according to the current theme status.
1046                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
1047                     optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
1048                 } else {
1049                     optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
1050                 }
1051             }
1052         }
1053
1054         // Done.
1055         return true;
1056     }
1057
1058     @Override
1059     public boolean onPrepareOptionsMenu(Menu menu) {
1060         // Get a handle for the cookie manager.
1061         CookieManager cookieManager = CookieManager.getInstance();
1062
1063         // Initialize the current user agent string and the font size.
1064         String currentUserAgent = getString(R.string.user_agent_privacy_browser);
1065         int fontSize = 100;
1066
1067         // 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.
1068         if (currentWebView != null) {
1069             // Set the add or edit domain text.
1070             if (currentWebView.getDomainSettingsApplied()) {
1071                 optionsAddOrEditDomainMenuItem.setTitle(R.string.edit_domain_settings);
1072             } else {
1073                 optionsAddOrEditDomainMenuItem.setTitle(R.string.add_domain_settings);
1074             }
1075
1076             // Get the current user agent from the WebView.
1077             currentUserAgent = currentWebView.getSettings().getUserAgentString();
1078
1079             // Get the current font size from the
1080             fontSize = currentWebView.getSettings().getTextZoom();
1081
1082             // Set the status of the menu item checkboxes.
1083             optionsDomStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1084             optionsSaveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
1085             optionsEasyListMenuItem.setChecked(currentWebView.getEasyListEnabled());
1086             optionsEasyPrivacyMenuItem.setChecked(currentWebView.getEasyPrivacyEnabled());
1087             optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.getFanboysAnnoyanceListEnabled());
1088             optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.getFanboysSocialBlockingListEnabled());
1089             optionsUltraListMenuItem.setChecked(currentWebView.getUltraListEnabled());
1090             optionsUltraPrivacyMenuItem.setChecked(currentWebView.getUltraPrivacyEnabled());
1091             optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.getBlockAllThirdPartyRequests());
1092             optionsSwipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
1093             optionsWideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
1094             optionsDisplayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
1095
1096             // Initialize the display names for the blocklists with the number of blocked requests.
1097             optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
1098             optionsEasyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
1099             optionsEasyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
1100             optionsFanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
1101             optionsFanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
1102             optionsUltraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
1103             optionsUltraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
1104             optionsBlockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
1105
1106             // Enable DOM Storage if JavaScript is enabled.
1107             optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
1108
1109             // Set the checkbox status for dark WebView if the WebView supports it.
1110             if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
1111                 optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
1112             }
1113         }
1114
1115         // Set the cookies menu item checked status.
1116         optionsCookiesMenuItem.setChecked(cookieManager.acceptCookie());
1117
1118         // Enable Clear Cookies if there are any.
1119         optionsClearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
1120
1121         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
1122         String privateDataDirectoryString = getApplicationInfo().dataDir;
1123
1124         // Get a count of the number of files in the Local Storage directory.
1125         File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
1126         int localStorageDirectoryNumberOfFiles = 0;
1127         if (localStorageDirectory.exists()) {
1128             // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
1129             localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
1130         }
1131
1132         // Get a count of the number of files in the IndexedDB directory.
1133         File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
1134         int indexedDBDirectoryNumberOfFiles = 0;
1135         if (indexedDBDirectory.exists()) {
1136             // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
1137             indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
1138         }
1139
1140         // Enable Clear DOM Storage if there is any.
1141         optionsClearDomStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
1142
1143         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
1144         if (Build.VERSION.SDK_INT < 26) {
1145             // Get the WebView database.
1146             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1147
1148             // Enable the clear form data menu item if there is anything to clear.
1149             optionsClearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
1150         }
1151
1152         // Enable Clear Data if any of the submenu items are enabled.
1153         optionsClearDataMenuItem.setEnabled(optionsClearCookiesMenuItem.isEnabled() || optionsClearDomStorageMenuItem.isEnabled() || optionsClearFormDataMenuItem.isEnabled());
1154
1155         // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
1156         optionsFanboysSocialBlockingListMenuItem.setEnabled(!optionsFanboysAnnoyanceListMenuItem.isChecked());
1157
1158         // Set the proxy title and check the applied proxy.
1159         switch (proxyMode) {
1160             case ProxyHelper.NONE:
1161                 // Set the proxy title.
1162                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
1163
1164                 // Check the proxy None radio button.
1165                 optionsProxyNoneMenuItem.setChecked(true);
1166                 break;
1167
1168             case ProxyHelper.TOR:
1169                 // Set the proxy title.
1170                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
1171
1172                 // Check the proxy Tor radio button.
1173                 optionsProxyTorMenuItem.setChecked(true);
1174                 break;
1175
1176             case ProxyHelper.I2P:
1177                 // Set the proxy title.
1178                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
1179
1180                 // Check the proxy I2P radio button.
1181                 optionsProxyI2pMenuItem.setChecked(true);
1182                 break;
1183
1184             case ProxyHelper.CUSTOM:
1185                 // Set the proxy title.
1186                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
1187
1188                 // Check the proxy Custom radio button.
1189                 optionsProxyCustomMenuItem.setChecked(true);
1190                 break;
1191         }
1192
1193         // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
1194         if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
1195             // Update the user agent menu item title.
1196             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
1197
1198             // Select the Privacy Browser radio box.
1199             optionsUserAgentPrivacyBrowserMenuItem.setChecked(true);
1200         } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
1201             // Update the user agent menu item title.
1202             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
1203
1204             // Select the WebView Default radio box.
1205             optionsUserAgentWebViewDefaultMenuItem.setChecked(true);
1206         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
1207             // Update the user agent menu item title.
1208             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
1209
1210             // Select the Firefox on Android radio box.
1211             optionsUserAgentFirefoxOnAndroidMenuItem.setChecked(true);
1212         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
1213             // Update the user agent menu item title.
1214             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
1215
1216             // Select the Chrome on Android radio box.
1217             optionsUserAgentChromeOnAndroidMenuItem.setChecked(true);
1218         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
1219             // Update the user agent menu item title.
1220             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
1221
1222             // Select the Safari on iOS radio box.
1223             optionsUserAgentSafariOnIosMenuItem.setChecked(true);
1224         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
1225             // Update the user agent menu item title.
1226             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
1227
1228             // Select the Firefox on Linux radio box.
1229             optionsUserAgentFirefoxOnLinuxMenuItem.setChecked(true);
1230         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
1231             // Update the user agent menu item title.
1232             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
1233
1234             // Select the Chromium on Linux radio box.
1235             optionsUserAgentChromiumOnLinuxMenuItem.setChecked(true);
1236         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
1237             // Update the user agent menu item title.
1238             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
1239
1240             // Select the Firefox on Windows radio box.
1241             optionsUserAgentFirefoxOnWindowsMenuItem.setChecked(true);
1242         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
1243             // Update the user agent menu item title.
1244             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
1245
1246             // Select the Chrome on Windows radio box.
1247             optionsUserAgentChromeOnWindowsMenuItem.setChecked(true);
1248         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
1249             // Update the user agent menu item title.
1250             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
1251
1252             // Select the Edge on Windows radio box.
1253             optionsUserAgentEdgeOnWindowsMenuItem.setChecked(true);
1254         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
1255             // Update the user agent menu item title.
1256             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
1257
1258             // Select the Internet on Windows radio box.
1259             optionsUserAgentInternetExplorerOnWindowsMenuItem.setChecked(true);
1260         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
1261             // Update the user agent menu item title.
1262             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
1263
1264             // Select the Safari on macOS radio box.
1265             optionsUserAgentSafariOnMacosMenuItem.setChecked(true);
1266         } else {  // Custom user agent.
1267             // Update the user agent menu item title.
1268             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
1269
1270             // Select the Custom radio box.
1271             optionsUserAgentCustomMenuItem.setChecked(true);
1272         }
1273
1274         // Set the font size title.
1275         optionsFontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
1276
1277         // Run all the other default commands.
1278         super.onPrepareOptionsMenu(menu);
1279
1280         // Display the menu.
1281         return true;
1282     }
1283
1284     @Override
1285     public boolean onOptionsItemSelected(MenuItem menuItem) {
1286         // Get a handle for the shared preferences.
1287         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1288
1289         // Get a handle for the cookie manager.
1290         CookieManager cookieManager = CookieManager.getInstance();
1291
1292         // Get the selected menu item ID.
1293         int menuItemId = menuItem.getItemId();
1294
1295         // Run the commands that correlate to the selected menu item.
1296         if (menuItemId == R.id.javascript) {  // JavaScript.
1297             // Toggle the JavaScript status.
1298             currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1299
1300             // Update the privacy icon.
1301             updatePrivacyIcons(true);
1302
1303             // Display a `Snackbar`.
1304             if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
1305                 Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1306             } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
1307                 Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1308             } else {  // Privacy mode.
1309                 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1310             }
1311
1312             // Reload the current WebView.
1313             currentWebView.reload();
1314
1315             // Consume the event.
1316             return true;
1317         } else if (menuItemId == R.id.refresh) {  // Refresh.
1318             // Run the command that correlates to the current status of the menu item.
1319             if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
1320                 // Reload the current WebView.
1321                 currentWebView.reload();
1322             } else {  // The stop button was pushed.
1323                 // Stop the loading of the WebView.
1324                 currentWebView.stopLoading();
1325             }
1326
1327             // Consume the event.
1328             return true;
1329         } else if (menuItemId == R.id.bookmarks) {  // Bookmarks.
1330             // Open the bookmarks drawer.
1331             drawerLayout.openDrawer(GravityCompat.END);
1332
1333             // Consume the event.
1334             return true;
1335         } else if (menuItemId == R.id.cookies) {  // Cookies.
1336             // Switch the first-party cookie status.
1337             cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1338
1339             // Store the cookie status.
1340             currentWebView.setAcceptCookies(cookieManager.acceptCookie());
1341
1342             // Update the menu checkbox.
1343             menuItem.setChecked(cookieManager.acceptCookie());
1344
1345             // Update the privacy icon.
1346             updatePrivacyIcons(true);
1347
1348             // Display a snackbar.
1349             if (cookieManager.acceptCookie()) {  // Cookies are enabled.
1350                 Snackbar.make(webViewPager, R.string.cookies_enabled, Snackbar.LENGTH_SHORT).show();
1351             } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
1352                 Snackbar.make(webViewPager, R.string.cookies_disabled, Snackbar.LENGTH_SHORT).show();
1353             } else {  // Privacy mode.
1354                 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1355             }
1356
1357             // Reload the current WebView.
1358             currentWebView.reload();
1359
1360             // Consume the event.
1361             return true;
1362         } else if (menuItemId == R.id.dom_storage) {  // DOM storage.
1363             // Toggle the status of domStorageEnabled.
1364             currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1365
1366             // Update the menu checkbox.
1367             menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1368
1369             // Update the privacy icon.
1370             updatePrivacyIcons(true);
1371
1372             // Display a snackbar.
1373             if (currentWebView.getSettings().getDomStorageEnabled()) {
1374                 Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1375             } else {
1376                 Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1377             }
1378
1379             // Reload the current WebView.
1380             currentWebView.reload();
1381
1382             // Consume the event.
1383             return true;
1384         } else if (menuItemId == R.id.save_form_data) {  // Form data.  This can be removed once the minimum API >= 26.
1385             // Switch the status of saveFormDataEnabled.
1386             currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1387
1388             // Update the menu checkbox.
1389             menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1390
1391             // Display a snackbar.
1392             if (currentWebView.getSettings().getSaveFormData()) {
1393                 Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1394             } else {
1395                 Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1396             }
1397
1398             // Update the privacy icon.
1399             updatePrivacyIcons(true);
1400
1401             // Reload the current WebView.
1402             currentWebView.reload();
1403
1404             // Consume the event.
1405             return true;
1406         } else if (menuItemId == R.id.clear_cookies) {  // Clear cookies.
1407             // Create a snackbar.
1408             Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1409                     .setAction(R.string.undo, v -> {
1410                         // Do nothing because everything will be handled by `onDismissed()` below.
1411                     })
1412                     .addCallback(new Snackbar.Callback() {
1413                         @Override
1414                         public void onDismissed(Snackbar snackbar, int event) {
1415                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1416                                 // Delete the cookies, which command varies by SDK.
1417                                 if (Build.VERSION.SDK_INT < 21) {
1418                                     cookieManager.removeAllCookie();
1419                                 } else {
1420                                     cookieManager.removeAllCookies(null);
1421                                 }
1422                             }
1423                         }
1424                     })
1425                     .show();
1426
1427             // Consume the event.
1428             return true;
1429         } else if (menuItemId == R.id.clear_dom_storage) {  // Clear DOM storage.
1430             // Create a snackbar.
1431             Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1432                     .setAction(R.string.undo, v -> {
1433                         // Do nothing because everything will be handled by `onDismissed()` below.
1434                     })
1435                     .addCallback(new Snackbar.Callback() {
1436                         @Override
1437                         public void onDismissed(Snackbar snackbar, int event) {
1438                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1439                                 // Delete the DOM Storage.
1440                                 WebStorage webStorage = WebStorage.getInstance();
1441                                 webStorage.deleteAllData();
1442
1443                                 // Initialize a handler to manually delete the DOM storage files and directories.
1444                                 Handler deleteDomStorageHandler = new Handler();
1445
1446                                 // Setup a runnable to manually delete the DOM storage files and directories.
1447                                 Runnable deleteDomStorageRunnable = () -> {
1448                                     try {
1449                                         // Get a handle for the runtime.
1450                                         Runtime runtime = Runtime.getRuntime();
1451
1452                                         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1453                                         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1454                                         String privateDataDirectoryString = getApplicationInfo().dataDir;
1455
1456                                         // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1457                                         Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1458
1459                                         // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1460                                         Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1461                                         Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1462                                         Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1463                                         Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1464
1465                                         // Wait for the processes to finish.
1466                                         deleteLocalStorageProcess.waitFor();
1467                                         deleteIndexProcess.waitFor();
1468                                         deleteQuotaManagerProcess.waitFor();
1469                                         deleteQuotaManagerJournalProcess.waitFor();
1470                                         deleteDatabasesProcess.waitFor();
1471                                     } catch (Exception exception) {
1472                                         // Do nothing if an error is thrown.
1473                                     }
1474                                 };
1475
1476                                 // Manually delete the DOM storage files after 200 milliseconds.
1477                                 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1478                             }
1479                         }
1480                     })
1481                     .show();
1482
1483             // Consume the event.
1484             return true;
1485         } else if (menuItemId == R.id.clear_form_data) {  // Clear form data.  This can be remove once the minimum API >= 26.
1486             // Create a snackbar.
1487             Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1488                     .setAction(R.string.undo, v -> {
1489                         // Do nothing because everything will be handled by `onDismissed()` below.
1490                     })
1491                     .addCallback(new Snackbar.Callback() {
1492                         @Override
1493                         public void onDismissed(Snackbar snackbar, int event) {
1494                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1495                                 // Get a handle for the webView database.
1496                                 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1497
1498                                 // Delete the form data.
1499                                 webViewDatabase.clearFormData();
1500                             }
1501                         }
1502                     })
1503                     .show();
1504
1505             // Consume the event.
1506             return true;
1507         } else if (menuItemId == R.id.easylist) {  // EasyList.
1508             // Toggle the EasyList status.
1509             currentWebView.setEasyListEnabled(!currentWebView.getEasyListEnabled());
1510
1511             // Update the menu checkbox.
1512             menuItem.setChecked(currentWebView.getEasyListEnabled());
1513
1514             // Reload the current WebView.
1515             currentWebView.reload();
1516
1517             // Consume the event.
1518             return true;
1519         } else if (menuItemId == R.id.easyprivacy) {  // EasyPrivacy.
1520             // Toggle the EasyPrivacy status.
1521             currentWebView.setEasyPrivacyEnabled(!currentWebView.getEasyPrivacyEnabled());
1522
1523             // Update the menu checkbox.
1524             menuItem.setChecked(currentWebView.getEasyPrivacyEnabled());
1525
1526             // Reload the current WebView.
1527             currentWebView.reload();
1528
1529             // Consume the event.
1530             return true;
1531         } else if (menuItemId == R.id.fanboys_annoyance_list) {  // Fanboy's Annoyance List.
1532             // Toggle Fanboy's Annoyance List status.
1533             currentWebView.setFanboysAnnoyanceListEnabled(!currentWebView.getFanboysAnnoyanceListEnabled());
1534
1535             // Update the menu checkbox.
1536             menuItem.setChecked(currentWebView.getFanboysAnnoyanceListEnabled());
1537
1538             // Update the status of Fanboy's Social Blocking List.
1539             optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.getFanboysAnnoyanceListEnabled());
1540
1541             // Reload the current WebView.
1542             currentWebView.reload();
1543
1544             // Consume the event.
1545             return true;
1546         } else if (menuItemId == R.id.fanboys_social_blocking_list) {  // Fanboy's Social Blocking List.
1547             // Toggle Fanboy's Social Blocking List status.
1548             currentWebView.setFanboysSocialBlockingListEnabled(!currentWebView.getFanboysSocialBlockingListEnabled());
1549
1550             // Update the menu checkbox.
1551             menuItem.setChecked(currentWebView.getFanboysSocialBlockingListEnabled());
1552
1553             // Reload the current WebView.
1554             currentWebView.reload();
1555
1556             // Consume the event.
1557             return true;
1558         } else if (menuItemId == R.id.ultralist) {  // UltraList.
1559             // Toggle the UltraList status.
1560             currentWebView.setUltraListEnabled(!currentWebView.getUltraListEnabled());
1561
1562             // Update the menu checkbox.
1563             menuItem.setChecked(currentWebView.getUltraListEnabled());
1564
1565             // Reload the current WebView.
1566             currentWebView.reload();
1567
1568             // Consume the event.
1569             return true;
1570         } else if (menuItemId == R.id.ultraprivacy) {  // UltraPrivacy.
1571             // Toggle the UltraPrivacy status.
1572             currentWebView.setUltraPrivacyEnabled(!currentWebView.getUltraPrivacyEnabled());
1573
1574             // Update the menu checkbox.
1575             menuItem.setChecked(currentWebView.getUltraPrivacyEnabled());
1576
1577             // Reload the current WebView.
1578             currentWebView.reload();
1579
1580             // Consume the event.
1581             return true;
1582         } else if (menuItemId == R.id.block_all_third_party_requests) {  // Block all third-party requests.
1583             //Toggle the third-party requests blocker status.
1584             currentWebView.setBlockAllThirdPartyRequests(!currentWebView.getBlockAllThirdPartyRequests());
1585
1586             // Update the menu checkbox.
1587             menuItem.setChecked(currentWebView.getBlockAllThirdPartyRequests());
1588
1589             // Reload the current WebView.
1590             currentWebView.reload();
1591
1592             // Consume the event.
1593             return true;
1594         } else if (menuItemId == R.id.proxy_none) {  // Proxy - None.
1595             // Update the proxy mode.
1596             proxyMode = ProxyHelper.NONE;
1597
1598             // Apply the proxy mode.
1599             applyProxy(true);
1600
1601             // Consume the event.
1602             return true;
1603         } else if (menuItemId == R.id.proxy_tor) {  // Proxy - Tor.
1604             // Update the proxy mode.
1605             proxyMode = ProxyHelper.TOR;
1606
1607             // Apply the proxy mode.
1608             applyProxy(true);
1609
1610             // Consume the event.
1611             return true;
1612         } else if (menuItemId == R.id.proxy_i2p) {  // Proxy - I2P.
1613             // Update the proxy mode.
1614             proxyMode = ProxyHelper.I2P;
1615
1616             // Apply the proxy mode.
1617             applyProxy(true);
1618
1619             // Consume the event.
1620             return true;
1621         } else if (menuItemId == R.id.proxy_custom) {  // Proxy - Custom.
1622             // Update the proxy mode.
1623             proxyMode = ProxyHelper.CUSTOM;
1624
1625             // Apply the proxy mode.
1626             applyProxy(true);
1627
1628             // Consume the event.
1629             return true;
1630         } else if (menuItemId == R.id.user_agent_privacy_browser) {  // User Agent - Privacy Browser.
1631             // Update the user agent.
1632             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1633
1634             // Reload the current WebView.
1635             currentWebView.reload();
1636
1637             // Consume the event.
1638             return true;
1639         } else if (menuItemId == R.id.user_agent_webview_default) {  // User Agent - WebView Default.
1640             // Update the user agent.
1641             currentWebView.getSettings().setUserAgentString("");
1642
1643             // Reload the current WebView.
1644             currentWebView.reload();
1645
1646             // Consume the event.
1647             return true;
1648         } else if (menuItemId == R.id.user_agent_firefox_on_android) {  // User Agent - Firefox on Android.
1649             // Update the user agent.
1650             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1651
1652             // Reload the current WebView.
1653             currentWebView.reload();
1654
1655             // Consume the event.
1656             return true;
1657         } else if (menuItemId == R.id.user_agent_chrome_on_android) {  // User Agent - Chrome on Android.
1658             // Update the user agent.
1659             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1660
1661             // Reload the current WebView.
1662             currentWebView.reload();
1663
1664             // Consume the event.
1665             return true;
1666         } else if (menuItemId == R.id.user_agent_safari_on_ios) {  // User Agent - Safari on iOS.
1667             // Update the user agent.
1668             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1669
1670             // Reload the current WebView.
1671             currentWebView.reload();
1672
1673             // Consume the event.
1674             return true;
1675         } else if (menuItemId == R.id.user_agent_firefox_on_linux) {  // User Agent - Firefox on Linux.
1676             // Update the user agent.
1677             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1678
1679             // Reload the current WebView.
1680             currentWebView.reload();
1681
1682             // Consume the event.
1683             return true;
1684         } else if (menuItemId == R.id.user_agent_chromium_on_linux) {  // User Agent - Chromium on Linux.
1685             // Update the user agent.
1686             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1687
1688             // Reload the current WebView.
1689             currentWebView.reload();
1690
1691             // Consume the event.
1692             return true;
1693         } else if (menuItemId == R.id.user_agent_firefox_on_windows) {  // User Agent - Firefox on Windows.
1694             // Update the user agent.
1695             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1696
1697             // Reload the current WebView.
1698             currentWebView.reload();
1699
1700             // Consume the event.
1701             return true;
1702         } else if (menuItemId == R.id.user_agent_chrome_on_windows) {  // User Agent - Chrome on Windows.
1703             // Update the user agent.
1704             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1705
1706             // Reload the current WebView.
1707             currentWebView.reload();
1708
1709             // Consume the event.
1710             return true;
1711         } else if (menuItemId == R.id.user_agent_edge_on_windows) {  // User Agent - Edge on Windows.
1712             // Update the user agent.
1713             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1714
1715             // Reload the current WebView.
1716             currentWebView.reload();
1717
1718             // Consume the event.
1719             return true;
1720         } else if (menuItemId == R.id.user_agent_internet_explorer_on_windows) {  // User Agent - Internet Explorer on Windows.
1721             // Update the user agent.
1722             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1723
1724             // Reload the current WebView.
1725             currentWebView.reload();
1726
1727             // Consume the event.
1728             return true;
1729         } else if (menuItemId == R.id.user_agent_safari_on_macos) {  // User Agent - Safari on macOS.
1730             // Update the user agent.
1731             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1732
1733             // Reload the current WebView.
1734             currentWebView.reload();
1735
1736             // Consume the event.
1737             return true;
1738         } else if (menuItemId == R.id.user_agent_custom) {  // User Agent - Custom.
1739             // Update the user agent.
1740             currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1741
1742             // Reload the current WebView.
1743             currentWebView.reload();
1744
1745             // Consume the event.
1746             return true;
1747         } else if (menuItemId == R.id.font_size) {  // Font size.
1748             // Instantiate the font size dialog.
1749             DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
1750
1751             // Show the font size dialog.
1752             fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
1753
1754             // Consume the event.
1755             return true;
1756         } else if (menuItemId == R.id.swipe_to_refresh) {  // Swipe to refresh.
1757             // Toggle the stored status of swipe to refresh.
1758             currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1759
1760             // Update the swipe refresh layout.
1761             if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
1762                 // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
1763                 swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
1764             } else {  // Swipe to refresh is disabled.
1765                 // Disable the swipe refresh layout.
1766                 swipeRefreshLayout.setEnabled(false);
1767             }
1768
1769             // Consume the event.
1770             return true;
1771         } else if (menuItemId == R.id.wide_viewport) {  // Wide viewport.
1772             // Toggle the viewport.
1773             currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1774
1775             // Consume the event.
1776             return true;
1777         } else if (menuItemId == R.id.display_images) {  // Display images.
1778             // Toggle the displaying of images.
1779             if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
1780                 // Disable loading of images.
1781                 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1782
1783                 // Reload the website to remove existing images.
1784                 currentWebView.reload();
1785             } else {  // Images are not currently loaded automatically.
1786                 // Enable loading of images.  Missing images will be loaded without the need for a reload.
1787                 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1788             }
1789
1790             // Consume the event.
1791             return true;
1792         } else if (menuItemId == R.id.dark_webview) {  // Dark WebView.
1793             // Check to see if dark WebView is supported by this WebView.
1794             if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
1795                 // Toggle the dark WebView setting.
1796                 if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) {  // Dark WebView is currently enabled.
1797                     // Turn off dark WebView.
1798                     WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
1799                 } else {  // Dark WebView is currently disabled.
1800                     // Turn on dark WebView.
1801                     WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
1802                 }
1803             }
1804
1805             // Consume the event.
1806             return true;
1807         } else if (menuItemId == R.id.find_on_page) {  // Find on page.
1808             // Get a handle for the views.
1809             Toolbar toolbar = findViewById(R.id.toolbar);
1810             LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1811             EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1812
1813             // Set the minimum height of the find on page linear layout to match the toolbar.
1814             findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1815
1816             // Hide the toolbar.
1817             toolbar.setVisibility(View.GONE);
1818
1819             // Show the find on page linear layout.
1820             findOnPageLinearLayout.setVisibility(View.VISIBLE);
1821
1822             // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
1823             // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1824             findOnPageEditText.postDelayed(() -> {
1825                 // Set the focus on the find on page edit text.
1826                 findOnPageEditText.requestFocus();
1827
1828                 // Get a handle for the input method manager.
1829                 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1830
1831                 // Remove the lint warning below that the input method manager might be null.
1832                 assert inputMethodManager != null;
1833
1834                 // Display the keyboard.  `0` sets no input flags.
1835                 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1836             }, 200);
1837
1838             // Consume the event.
1839             return true;
1840         } else if (menuItemId == R.id.print) {  // Print.
1841             // Get a print manager instance.
1842             PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1843
1844             // Remove the lint error below that print manager might be null.
1845             assert printManager != null;
1846
1847             // Create a print document adapter from the current WebView.
1848             PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1849
1850             // Print the document.
1851             printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
1852
1853             // Consume the event.
1854             return true;
1855         } else if (menuItemId == R.id.save_url) {  // Save URL.
1856             // Check the download preference.
1857             if (downloadWithExternalApp) {  // Download with an external app.
1858                 downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
1859             } else {  // Handle the download inside of Privacy Browser.
1860                 // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
1861                 new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
1862                         currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
1863             }
1864
1865             // Consume the event.
1866             return true;
1867         } else if (menuItemId == R.id.save_archive) {
1868             // Open the file picker with a default file name built from the current domain name.
1869             saveWebpageArchiveActivityResultLauncher.launch(currentWebView.getCurrentDomainName() + ".mht");
1870
1871             // Consume the event.
1872             return true;
1873         } else if (menuItemId == R.id.save_image) {  // Save image.
1874             // Open the file picker with a default file name built from the current domain name.
1875             saveWebpageImageActivityResultLauncher.launch(currentWebView.getCurrentDomainName() + ".png");
1876
1877             // Consume the event.
1878             return true;
1879         } else if (menuItemId == R.id.add_to_homescreen) {  // Add to homescreen.
1880             // Instantiate the create home screen shortcut dialog.
1881             DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1882                     currentWebView.getFavoriteOrDefaultIcon());
1883
1884             // Show the create home screen shortcut dialog.
1885             createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1886
1887             // Consume the event.
1888             return true;
1889         } else if (menuItemId == R.id.view_source) {  // View source.
1890             // Create an intent to launch the view source activity.
1891             Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1892
1893             // Add the variables to the intent.
1894             viewSourceIntent.putExtra(ViewSourceActivityKt.CURRENT_URL, currentWebView.getUrl());
1895             viewSourceIntent.putExtra(ViewSourceActivityKt.USER_AGENT, currentWebView.getSettings().getUserAgentString());
1896
1897             // Make it so.
1898             startActivity(viewSourceIntent);
1899
1900             // Consume the event.
1901             return true;
1902         } else if (menuItemId == R.id.share_url) {  // Share URL.
1903             // Setup the share string.
1904             String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1905
1906             // Create the share intent.
1907             Intent shareIntent = new Intent(Intent.ACTION_SEND);
1908
1909             // Add the share string to the intent.
1910             shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1911
1912             // Set the MIME type.
1913             shareIntent.setType("text/plain");
1914
1915             // Set the intent to open in a new task.
1916             shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1917
1918             // Make it so.
1919             startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1920
1921             // Consume the event.
1922             return true;
1923         } else if (menuItemId == R.id.open_with_app) {  // Open with app.
1924             // Open the URL with an outside app.
1925             openWithApp(currentWebView.getUrl());
1926
1927             // Consume the event.
1928             return true;
1929         } else if (menuItemId == R.id.open_with_browser) {  // Open with browser.
1930             // Open the URL with an outside browser.
1931             openWithBrowser(currentWebView.getUrl());
1932
1933             // Consume the event.
1934             return true;
1935         } else if (menuItemId == R.id.add_or_edit_domain) {  // Add or edit domain.
1936             // Check if domain settings currently exist.
1937             if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
1938                 // Reapply the domain settings on returning to `MainWebViewActivity`.
1939                 reapplyDomainSettingsOnRestart = true;
1940
1941                 // Create an intent to launch the domains activity.
1942                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1943
1944                 // Add the extra information to the intent.
1945                 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1946                 domainsIntent.putExtra("close_on_back", true);
1947                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1948                 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1949
1950                 // Get the current certificate.
1951                 SslCertificate sslCertificate = currentWebView.getCertificate();
1952
1953                 // Check to see if the SSL certificate is populated.
1954                 if (sslCertificate != null) {
1955                     // Extract the certificate to strings.
1956                     String issuedToCName = sslCertificate.getIssuedTo().getCName();
1957                     String issuedToOName = sslCertificate.getIssuedTo().getOName();
1958                     String issuedToUName = sslCertificate.getIssuedTo().getUName();
1959                     String issuedByCName = sslCertificate.getIssuedBy().getCName();
1960                     String issuedByOName = sslCertificate.getIssuedBy().getOName();
1961                     String issuedByUName = sslCertificate.getIssuedBy().getUName();
1962                     long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1963                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1964
1965                     // Add the certificate to the intent.
1966                     domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1967                     domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1968                     domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1969                     domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1970                     domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1971                     domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1972                     domainsIntent.putExtra("ssl_start_date", startDateLong);
1973                     domainsIntent.putExtra("ssl_end_date", endDateLong);
1974                 }
1975
1976                 // Make it so.
1977                 startActivity(domainsIntent);
1978             } else {  // Add a new domain.
1979                 // Apply the new domain settings on returning to `MainWebViewActivity`.
1980                 reapplyDomainSettingsOnRestart = true;
1981
1982                 // Get the current domain
1983                 Uri currentUri = Uri.parse(currentWebView.getUrl());
1984                 String currentDomain = currentUri.getHost();
1985
1986                 // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1987                 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1988
1989                 // Create the domain and store the database ID.
1990                 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1991
1992                 // Create an intent to launch the domains activity.
1993                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1994
1995                 // Add the extra information to the intent.
1996                 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1997                 domainsIntent.putExtra("close_on_back", true);
1998                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1999                 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2000
2001                 // Get the current certificate.
2002                 SslCertificate sslCertificate = currentWebView.getCertificate();
2003
2004                 // Check to see if the SSL certificate is populated.
2005                 if (sslCertificate != null) {
2006                     // Extract the certificate to strings.
2007                     String issuedToCName = sslCertificate.getIssuedTo().getCName();
2008                     String issuedToOName = sslCertificate.getIssuedTo().getOName();
2009                     String issuedToUName = sslCertificate.getIssuedTo().getUName();
2010                     String issuedByCName = sslCertificate.getIssuedBy().getCName();
2011                     String issuedByOName = sslCertificate.getIssuedBy().getOName();
2012                     String issuedByUName = sslCertificate.getIssuedBy().getUName();
2013                     long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2014                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2015
2016                     // Add the certificate to the intent.
2017                     domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2018                     domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2019                     domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2020                     domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2021                     domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2022                     domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2023                     domainsIntent.putExtra("ssl_start_date", startDateLong);
2024                     domainsIntent.putExtra("ssl_end_date", endDateLong);
2025                 }
2026
2027                 // Make it so.
2028                 startActivity(domainsIntent);
2029             }
2030
2031             // Consume the event.
2032             return true;
2033         } else if (menuItemId == R.id.ad_consent) {  // Ad consent.
2034             // Instantiate the ad consent dialog.
2035             DialogFragment adConsentDialogFragment = new AdConsentDialog();
2036
2037             // Display the ad consent dialog.
2038             adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
2039
2040             // Consume the event.
2041             return true;
2042         } else {  // There is no match with the options menu.  Pass the event up to the parent method.
2043             // Don't consume the event.
2044             return super.onOptionsItemSelected(menuItem);
2045         }
2046     }
2047
2048     // removeAllCookies is deprecated, but it is required for API < 21.
2049     @Override
2050     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2051         // Get a handle for the shared preferences.
2052         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2053
2054         // Get the menu item ID.
2055         int menuItemId = menuItem.getItemId();
2056
2057         // Run the commands that correspond to the selected menu item.
2058         if (menuItemId == R.id.clear_and_exit) {  // Clear and exit.
2059             // Clear and exit Privacy Browser.
2060             clearAndExit();
2061         } else if (menuItemId == R.id.home) {  // Home.
2062             // Load the homepage.
2063             loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
2064         } else if (menuItemId == R.id.back) {  // Back.
2065             // Check if the WebView can go back.
2066             if (currentWebView.canGoBack()) {
2067                 // Get the current web back forward list.
2068                 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2069
2070                 // Get the previous entry URL.
2071                 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2072
2073                 // Apply the domain settings.
2074                 applyDomainSettings(currentWebView, previousUrl, false, false, false);
2075
2076                 // Load the previous website in the history.
2077                 currentWebView.goBack();
2078             }
2079         } else if (menuItemId == R.id.forward) {  // Forward.
2080             // Check if the WebView can go forward.
2081             if (currentWebView.canGoForward()) {
2082                 // Get the current web back forward list.
2083                 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2084
2085                 // Get the next entry URL.
2086                 String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
2087
2088                 // Apply the domain settings.
2089                 applyDomainSettings(currentWebView, nextUrl, false, false, false);
2090
2091                 // Load the next website in the history.
2092                 currentWebView.goForward();
2093             }
2094         } else if (menuItemId == R.id.history) {  // History.
2095             // Instantiate the URL history dialog.
2096             DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
2097
2098             // Show the URL history dialog.
2099             urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2100         } else if (menuItemId == R.id.open) {  // Open.
2101             // Instantiate the open file dialog.
2102             DialogFragment openDialogFragment = new OpenDialog();
2103
2104             // Show the open file dialog.
2105             openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
2106         } else if (menuItemId == R.id.requests) {  // Requests.
2107             // Populate the resource requests.
2108             RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
2109
2110             // Create an intent to launch the Requests activity.
2111             Intent requestsIntent = new Intent(this, RequestsActivity.class);
2112
2113             // Add the block third-party requests status to the intent.
2114             requestsIntent.putExtra("block_all_third_party_requests", currentWebView.getBlockAllThirdPartyRequests());
2115
2116             // Make it so.
2117             startActivity(requestsIntent);
2118         } else if (menuItemId == R.id.downloads) {  // Downloads.
2119             // Try the default system download manager.
2120             try {
2121                 // Launch the default system Download Manager.
2122                 Intent defaultDownloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2123
2124                 // Launch as a new task so that the download manager and Privacy Browser show as separate windows in the recent tasks list.
2125                 defaultDownloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2126
2127                 // Make it so.
2128                 startActivity(defaultDownloadManagerIntent);
2129             } catch (Exception defaultDownloadManagerException) {
2130                 // Try a generic file manager.
2131                 try {
2132                     // Create a generic file manager intent.
2133                     Intent genericFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2134
2135                     // Open the download directory.
2136                     genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR);
2137
2138                     // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2139                     genericFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2140
2141                     // Make it so.
2142                     startActivity(genericFileManagerIntent);
2143                 } catch (Exception genericFileManagerException) {
2144                     // Try an alternate file manager.
2145                     try {
2146                         // Create an alternate file manager intent.
2147                         Intent alternateFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2148
2149                         // Open the download directory.
2150                         alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder");
2151
2152                         // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2153                         alternateFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2154
2155                         // Open the alternate file manager.
2156                         startActivity(alternateFileManagerIntent);
2157                     } catch (Exception alternateFileManagerException) {
2158                         // Display a snackbar.
2159                         Snackbar.make(currentWebView, R.string.no_file_manager_detected, Snackbar.LENGTH_INDEFINITE).show();
2160                     }
2161                 }
2162             }
2163         } else if (menuItemId == R.id.domains) {  // Domains.
2164             // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2165             reapplyDomainSettingsOnRestart = true;
2166
2167             // Launch the domains activity.
2168             Intent domainsIntent = new Intent(this, DomainsActivity.class);
2169
2170             // Add the extra information to the intent.
2171             domainsIntent.putExtra("current_url", currentWebView.getUrl());
2172             domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2173
2174             // Get the current certificate.
2175             SslCertificate sslCertificate = currentWebView.getCertificate();
2176
2177             // Check to see if the SSL certificate is populated.
2178             if (sslCertificate != null) {
2179                 // Extract the certificate to strings.
2180                 String issuedToCName = sslCertificate.getIssuedTo().getCName();
2181                 String issuedToOName = sslCertificate.getIssuedTo().getOName();
2182                 String issuedToUName = sslCertificate.getIssuedTo().getUName();
2183                 String issuedByCName = sslCertificate.getIssuedBy().getCName();
2184                 String issuedByOName = sslCertificate.getIssuedBy().getOName();
2185                 String issuedByUName = sslCertificate.getIssuedBy().getUName();
2186                 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2187                 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2188
2189                 // Add the certificate to the intent.
2190                 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2191                 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2192                 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2193                 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2194                 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2195                 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2196                 domainsIntent.putExtra("ssl_start_date", startDateLong);
2197                 domainsIntent.putExtra("ssl_end_date", endDateLong);
2198             }
2199
2200             // Make it so.
2201             startActivity(domainsIntent);
2202         } else if (menuItemId == R.id.settings) {  // Settings.
2203             // Set the flag to reapply app settings on restart when returning from Settings.
2204             reapplyAppSettingsOnRestart = true;
2205
2206             // Set the flag to reapply the domain settings on restart when returning from Settings.
2207             reapplyDomainSettingsOnRestart = true;
2208
2209             // Launch the settings activity.
2210             Intent settingsIntent = new Intent(this, SettingsActivity.class);
2211             startActivity(settingsIntent);
2212         } else if (menuItemId == R.id.import_export) { // Import/Export.
2213             // Create an intent to launch the import/export activity.
2214             Intent importExportIntent = new Intent(this, ImportExportActivity.class);
2215
2216             // Make it so.
2217             startActivity(importExportIntent);
2218         } else if (menuItemId == R.id.logcat) {  // Logcat.
2219             // Create an intent to launch the logcat activity.
2220             Intent logcatIntent = new Intent(this, LogcatActivity.class);
2221
2222             // Make it so.
2223             startActivity(logcatIntent);
2224         } else if (menuItemId == R.id.guide) {  // Guide.
2225             // Create an intent to launch the guide activity.
2226             Intent guideIntent = new Intent(this, GuideActivity.class);
2227
2228             // Make it so.
2229             startActivity(guideIntent);
2230         } else if (menuItemId == R.id.about) {  // About
2231             // Create an intent to launch the about activity.
2232             Intent aboutIntent = new Intent(this, AboutActivity.class);
2233
2234             // Create a string array for the blocklist versions.
2235             String[] blocklistVersions = new String[]{easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0],
2236                     ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
2237
2238             // Add the blocklist versions to the intent.
2239             aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions);
2240
2241             // Make it so.
2242             startActivity(aboutIntent);
2243         }
2244
2245         // Close the navigation drawer.
2246         drawerLayout.closeDrawer(GravityCompat.START);
2247         return true;
2248     }
2249
2250     @Override
2251     public void onPostCreate(Bundle savedInstanceState) {
2252         // Run the default commands.
2253         super.onPostCreate(savedInstanceState);
2254
2255         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
2256         actionBarDrawerToggle.syncState();
2257     }
2258
2259     @Override
2260     public void onConfigurationChanged(@NonNull Configuration newConfig) {
2261         // Run the default commands.
2262         super.onConfigurationChanged(newConfig);
2263
2264         // Reload the ad for the free flavor if not in full screen mode.
2265         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2266             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
2267             View adView = findViewById(R.id.adview);
2268
2269             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2270             // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
2271             AdHelper.loadAd(adView, getApplicationContext(), this, getString(R.string.ad_unit_id));
2272         }
2273
2274         // `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:
2275         // https://code.google.com/p/android/issues/detail?id=20493#c8
2276         // ActivityCompat.invalidateOptionsMenu(this);
2277     }
2278
2279     @Override
2280     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2281         // Get the hit test result.
2282         final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2283
2284         // Define the URL strings.
2285         final String imageUrl;
2286         final String linkUrl;
2287
2288         // Get handles for the system managers.
2289         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2290
2291         // Remove the lint errors below that the clipboard manager might be null.
2292         assert clipboardManager != null;
2293
2294         // Process the link according to the type.
2295         switch (hitTestResult.getType()) {
2296             // `SRC_ANCHOR_TYPE` is a link.
2297             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2298                 // Get the target URL.
2299                 linkUrl = hitTestResult.getExtra();
2300
2301                 // Set the target URL as the title of the `ContextMenu`.
2302                 menu.setHeaderTitle(linkUrl);
2303
2304                 // Add an Open in New Tab entry.
2305                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2306                     // Load the link URL in a new tab and move to it.
2307                     addNewTab(linkUrl, true);
2308
2309                     // Consume the event.
2310                     return true;
2311                 });
2312
2313                 // Add an Open in Background entry.
2314                 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2315                     // Load the link URL in a new tab but do not move to it.
2316                     addNewTab(linkUrl, false);
2317
2318                     // Consume the event.
2319                     return true;
2320                 });
2321
2322                 // Add an Open with App entry.
2323                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2324                     openWithApp(linkUrl);
2325
2326                     // Consume the event.
2327                     return true;
2328                 });
2329
2330                 // Add an Open with Browser entry.
2331                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2332                     openWithBrowser(linkUrl);
2333
2334                     // Consume the event.
2335                     return true;
2336                 });
2337
2338                 // Add a Copy URL entry.
2339                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2340                     // Save the link URL in a `ClipData`.
2341                     ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2342
2343                     // Set the `ClipData` as the clipboard's primary clip.
2344                     clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2345
2346                     // Consume the event.
2347                     return true;
2348                 });
2349
2350                 // Add a Save URL entry.
2351                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2352                     // Check the download preference.
2353                     if (downloadWithExternalApp) {  // Download with an external app.
2354                         downloadUrlWithExternalApp(linkUrl);
2355                     } else {  // Handle the download inside of Privacy Browser.
2356                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2357                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
2358                                 currentWebView.getAcceptCookies()).execute(linkUrl);
2359                     }
2360
2361                     // Consume the event.
2362                     return true;
2363                 });
2364
2365                 // Add an empty Cancel entry, which by default closes the context menu.
2366                 menu.add(R.string.cancel);
2367                 break;
2368
2369             // `IMAGE_TYPE` is an image.
2370             case WebView.HitTestResult.IMAGE_TYPE:
2371                 // Get the image URL.
2372                 imageUrl = hitTestResult.getExtra();
2373
2374                 // Remove the incorrect lint warning below that the image URL might be null.
2375                 assert imageUrl != null;
2376
2377                 // Set the context menu title.
2378                 if (imageUrl.startsWith("data:")) {  // The image data is contained in within the URL, making it exceedingly long.
2379                     // Truncate the image URL before making it the title.
2380                     menu.setHeaderTitle(imageUrl.substring(0, 100));
2381                 } else {  // The image URL does not contain the full image data.
2382                     // Set the image URL as the title of the context menu.
2383                     menu.setHeaderTitle(imageUrl);
2384                 }
2385
2386                 // Add an Open in New Tab entry.
2387                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2388                     // Load the image in a new tab.
2389                     addNewTab(imageUrl, true);
2390
2391                     // Consume the event.
2392                     return true;
2393                 });
2394
2395                 // Add an Open with App entry.
2396                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2397                     // Open the image URL with an external app.
2398                     openWithApp(imageUrl);
2399
2400                     // Consume the event.
2401                     return true;
2402                 });
2403
2404                 // Add an Open with Browser entry.
2405                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2406                     // Open the image URL with an external browser.
2407                     openWithBrowser(imageUrl);
2408
2409                     // Consume the event.
2410                     return true;
2411                 });
2412
2413                 // Add a View Image entry.
2414                 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2415                     // Load the image in the current tab.
2416                     loadUrl(currentWebView, imageUrl);
2417
2418                     // Consume the event.
2419                     return true;
2420                 });
2421
2422                 // Add a Save Image entry.
2423                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2424                     // Check the download preference.
2425                     if (downloadWithExternalApp) {  // Download with an external app.
2426                         downloadUrlWithExternalApp(imageUrl);
2427                     } else {  // Handle the download inside of Privacy Browser.
2428                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2429                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
2430                                 currentWebView.getAcceptCookies()).execute(imageUrl);
2431                     }
2432
2433                     // Consume the event.
2434                     return true;
2435                 });
2436
2437                 // Add a Copy URL entry.
2438                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2439                     // Save the image URL in a clip data.
2440                     ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2441
2442                     // Set the clip data as the clipboard's primary clip.
2443                     clipboardManager.setPrimaryClip(imageTypeClipData);
2444
2445                     // Consume the event.
2446                     return true;
2447                 });
2448
2449                 // Add an empty Cancel entry, which by default closes the context menu.
2450                 menu.add(R.string.cancel);
2451                 break;
2452
2453             // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2454             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2455                 // Get the image URL.
2456                 imageUrl = hitTestResult.getExtra();
2457
2458                 // Instantiate a handler.
2459                 Handler handler = new Handler();
2460
2461                 // Get a message from the handler.
2462                 Message message = handler.obtainMessage();
2463
2464                 // Request the image details from the last touched node be returned in the message.
2465                 currentWebView.requestFocusNodeHref(message);
2466
2467                 // Get the link URL from the message data.
2468                 linkUrl = message.getData().getString("url");
2469
2470                 // Set the link URL as the title of the context menu.
2471                 menu.setHeaderTitle(linkUrl);
2472
2473                 // Add an Open in New Tab entry.
2474                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2475                     // Load the link URL in a new tab and move to it.
2476                     addNewTab(linkUrl, true);
2477
2478                     // Consume the event.
2479                     return true;
2480                 });
2481
2482                 // Add an Open in Background entry.
2483                 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2484                     // Lod the link URL in a new tab but do not move to it.
2485                     addNewTab(linkUrl, false);
2486
2487                     // Consume the event.
2488                     return true;
2489                 });
2490
2491                 // Add an Open Image in New Tab entry.
2492                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2493                     // Load the image in a new tab and move to it.
2494                     addNewTab(imageUrl, true);
2495
2496                     // Consume the event.
2497                     return true;
2498                 });
2499
2500                 // Add an Open with App entry.
2501                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2502                     // Open the link URL with an external app.
2503                     openWithApp(linkUrl);
2504
2505                     // Consume the event.
2506                     return true;
2507                 });
2508
2509                 // Add an Open with Browser entry.
2510                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2511                     // Open the link URL with an external browser.
2512                     openWithBrowser(linkUrl);
2513
2514                     // Consume the event.
2515                     return true;
2516                 });
2517
2518                 // Add a View Image entry.
2519                 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2520                    // View the image in the current tab.
2521                    loadUrl(currentWebView, imageUrl);
2522
2523                    // Consume the event.
2524                    return true;
2525                 });
2526
2527                 // Add a Save Image entry.
2528                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2529                     // Check the download preference.
2530                     if (downloadWithExternalApp) {  // Download with an external app.
2531                         downloadUrlWithExternalApp(imageUrl);
2532                     } else {  // Handle the download inside of Privacy Browser.
2533                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2534                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
2535                                 currentWebView.getAcceptCookies()).execute(imageUrl);
2536                     }
2537
2538                     // Consume the event.
2539                     return true;
2540                 });
2541
2542                 // Add a Copy URL entry.
2543                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2544                     // Save the link URL in a clip data.
2545                     ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2546
2547                     // Set the clip data as the clipboard's primary clip.
2548                     clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2549
2550                     // Consume the event.
2551                     return true;
2552                 });
2553
2554                 // Add a Save URL entry.
2555                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2556                     // Check the download preference.
2557                     if (downloadWithExternalApp) {  // Download with an external app.
2558                         downloadUrlWithExternalApp(linkUrl);
2559                     } else {  // Handle the download inside of Privacy Browser.
2560                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2561                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
2562                                 currentWebView.getAcceptCookies()).execute(linkUrl);
2563                     }
2564
2565                     // Consume the event.
2566                     return true;
2567                 });
2568
2569                 // Add an empty Cancel entry, which by default closes the context menu.
2570                 menu.add(R.string.cancel);
2571                 break;
2572
2573             case WebView.HitTestResult.EMAIL_TYPE:
2574                 // Get the target URL.
2575                 linkUrl = hitTestResult.getExtra();
2576
2577                 // Set the target URL as the title of the `ContextMenu`.
2578                 menu.setHeaderTitle(linkUrl);
2579
2580                 // Add a Write Email entry.
2581                 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2582                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2583                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2584
2585                     // Parse the url and set it as the data for the `Intent`.
2586                     emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2587
2588                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2589                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2590
2591                     try {
2592                         // Make it so.
2593                         startActivity(emailIntent);
2594                     } catch (ActivityNotFoundException exception) {
2595                         // Display a snackbar.
2596                         Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
2597                     }
2598
2599                     // Consume the event.
2600                     return true;
2601                 });
2602
2603                 // Add a Copy Email Address entry.
2604                 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2605                     // Save the email address in a `ClipData`.
2606                     ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2607
2608                     // Set the `ClipData` as the clipboard's primary clip.
2609                     clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2610
2611                     // Consume the event.
2612                     return true;
2613                 });
2614
2615                 // Add an empty Cancel entry, which by default closes the context menu.
2616                 menu.add(R.string.cancel);
2617                 break;
2618         }
2619     }
2620
2621     @Override
2622     public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2623         // Get a handle for the bookmarks list view.
2624         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2625
2626         // Get the dialog.
2627         Dialog dialog = dialogFragment.getDialog();
2628
2629         // Remove the incorrect lint warning below that the dialog might be null.
2630         assert dialog != null;
2631
2632         // Get the views from the dialog fragment.
2633         EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2634         EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2635
2636         // Extract the strings from the edit texts.
2637         String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2638         String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2639
2640         // Create a favorite icon byte array output stream.
2641         ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2642
2643         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2644         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2645
2646         // Convert the favorite icon byte array stream to a byte array.
2647         byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2648
2649         // Display the new bookmark below the current items in the (0 indexed) list.
2650         int newBookmarkDisplayOrder = bookmarksListView.getCount();
2651
2652         // Create the bookmark.
2653         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2654
2655         // Update the bookmarks cursor with the current contents of this folder.
2656         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2657
2658         // Update the list view.
2659         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2660
2661         // Scroll to the new bookmark.
2662         bookmarksListView.setSelection(newBookmarkDisplayOrder);
2663     }
2664
2665     @Override
2666     public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
2667         // Get a handle for the bookmarks list view.
2668         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2669
2670         // Get the dialog.
2671         Dialog dialog = dialogFragment.getDialog();
2672
2673         // Remove the incorrect lint warning below that the dialog might be null.
2674         assert dialog != null;
2675
2676         // Get handles for the views in the dialog fragment.
2677         EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2678         RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2679         ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2680
2681         // Get new folder name string.
2682         String folderNameString = folderNameEditText.getText().toString();
2683
2684         // Create a folder icon bitmap.
2685         Bitmap folderIconBitmap;
2686
2687         // Set the folder icon bitmap according to the dialog.
2688         if (defaultIconRadioButton.isChecked()) {  // Use the default folder icon.
2689             // Get the default folder icon drawable.
2690             Drawable folderIconDrawable = defaultIconImageView.getDrawable();
2691
2692             // Convert the folder icon drawable to a bitmap drawable.
2693             BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2694
2695             // Convert the folder icon bitmap drawable to a bitmap.
2696             folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2697         } else {  // Use the WebView favorite icon.
2698             // Copy the favorite icon bitmap to the folder icon bitmap.
2699             folderIconBitmap = favoriteIconBitmap;
2700         }
2701
2702         // Create a folder icon byte array output stream.
2703         ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2704
2705         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2706         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2707
2708         // Convert the folder icon byte array stream to a byte array.
2709         byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2710
2711         // Move all the bookmarks down one in the display order.
2712         for (int i = 0; i < bookmarksListView.getCount(); i++) {
2713             int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2714             bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2715         }
2716
2717         // Create the folder, which will be placed at the top of the `ListView`.
2718         bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2719
2720         // Update the bookmarks cursor with the current contents of this folder.
2721         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2722
2723         // Update the `ListView`.
2724         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2725
2726         // Scroll to the new folder.
2727         bookmarksListView.setSelection(0);
2728     }
2729
2730     @Override
2731     public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
2732         // Remove the incorrect lint warning below that the dialog fragment might be null.
2733         assert dialogFragment != null;
2734
2735         // Get the dialog.
2736         Dialog dialog = dialogFragment.getDialog();
2737
2738         // Remove the incorrect lint warning below that the dialog might be null.
2739         assert dialog != null;
2740
2741         // Get handles for the views from the dialog.
2742         RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
2743         RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2744         ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2745         EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2746
2747         // Get the new folder name.
2748         String newFolderNameString = editFolderNameEditText.getText().toString();
2749
2750         // Check if the favorite icon has changed.
2751         if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
2752             // Update the name in the database.
2753             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2754         } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
2755             // Create the new folder icon Bitmap.
2756             Bitmap folderIconBitmap;
2757
2758             // Populate the new folder icon bitmap.
2759             if (defaultFolderIconRadioButton.isChecked()) {
2760                 // Get the default folder icon drawable.
2761                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2762
2763                 // Convert the folder icon drawable to a bitmap drawable.
2764                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2765
2766                 // Convert the folder icon bitmap drawable to a bitmap.
2767                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2768             } else {  // Use the `WebView` favorite icon.
2769                 // Copy the favorite icon bitmap to the folder icon bitmap.
2770                 folderIconBitmap = favoriteIconBitmap;
2771             }
2772
2773             // Create a folder icon byte array output stream.
2774             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2775
2776             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2777             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2778
2779             // Convert the folder icon byte array stream to a byte array.
2780             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2781
2782             // Update the folder icon in the database.
2783             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2784         } else {  // The folder icon and the name have changed.
2785             // Get the new folder icon bitmap.
2786             Bitmap folderIconBitmap;
2787             if (defaultFolderIconRadioButton.isChecked()) {
2788                 // Get the default folder icon drawable.
2789                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2790
2791                 // Convert the folder icon drawable to a bitmap drawable.
2792                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2793
2794                 // Convert the folder icon bitmap drawable to a bitmap.
2795                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2796             } else {  // Use the `WebView` favorite icon.
2797                 // Copy the favorite icon bitmap to the folder icon bitmap.
2798                 folderIconBitmap = favoriteIconBitmap;
2799             }
2800
2801             // Create a folder icon byte array output stream.
2802             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2803
2804             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2805             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2806
2807             // Convert the folder icon byte array stream to a byte array.
2808             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2809
2810             // Update the folder name and icon in the database.
2811             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2812         }
2813
2814         // Update the bookmarks cursor with the current contents of this folder.
2815         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2816
2817         // Update the `ListView`.
2818         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2819     }
2820
2821     // Override `onBackPressed()` to handle the navigation drawer and and the WebViews.
2822     @Override
2823     public void onBackPressed() {
2824         // Check the different options for processing `back`.
2825         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
2826             // Close the navigation drawer.
2827             drawerLayout.closeDrawer(GravityCompat.START);
2828         } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
2829             // close the bookmarks drawer.
2830             drawerLayout.closeDrawer(GravityCompat.END);
2831         } else if (displayingFullScreenVideo) {  // A full screen video is shown.
2832             // Exit the full screen video.
2833             exitFullScreenVideo();
2834         } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
2835             // Get the current web back forward list.
2836             WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2837
2838             // Get the previous entry URL.
2839             String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2840
2841             // Apply the domain settings.
2842             applyDomainSettings(currentWebView, previousUrl, false, false, false);
2843
2844             // Go back.
2845             currentWebView.goBack();
2846         } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
2847             // Close the current tab.
2848             closeCurrentTab();
2849         } else {  // There isn't anything to do in Privacy Browser.
2850             // Close Privacy Browser.  `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
2851             if (Build.VERSION.SDK_INT >= 21) {
2852                 finishAndRemoveTask();
2853             } else {
2854                 finish();
2855             }
2856
2857             // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
2858             System.exit(0);
2859         }
2860     }
2861
2862     // Process the results of a file browse.
2863     @Override
2864     public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
2865         // Run the default commands.
2866         super.onActivityResult(requestCode, resultCode, returnedIntent);
2867
2868         // Run the commands that correlate to the specified request code.
2869         switch (requestCode) {
2870             case BROWSE_FILE_UPLOAD_REQUEST_CODE:
2871                 // File uploads only work on API >= 21.
2872                 if (Build.VERSION.SDK_INT >= 21) {
2873                     // Pass the file to the WebView.
2874                     fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
2875                 }
2876                 break;
2877
2878             case BROWSE_OPEN_REQUEST_CODE:
2879                 // Don't do anything if the user pressed back from the file picker.
2880                 if (resultCode == Activity.RESULT_OK) {
2881                     // Get a handle for the open dialog fragment.
2882                     DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
2883
2884                     // Only update the file name if the dialog still exists.
2885                     if (openDialogFragment != null) {
2886                         // Get a handle for the open dialog.
2887                         Dialog openDialog = openDialogFragment.getDialog();
2888
2889                         // Remove the incorrect lint warning below that the dialog might be null.
2890                         assert openDialog != null;
2891
2892                         // Get a handle for the file name edit text.
2893                         EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
2894
2895                         // Get the file name URI from the intent.
2896                         Uri fileNameUri = returnedIntent.getData();
2897
2898                         // Get the file name string from the URI.
2899                         String fileNameString = fileNameUri.toString();
2900
2901                         // Set the file name text.
2902                         fileNameEditText.setText(fileNameString);
2903
2904                         // Move the cursor to the end of the file name edit text.
2905                         fileNameEditText.setSelection(fileNameString.length());
2906                     }
2907                 }
2908                 break;
2909         }
2910     }
2911
2912     private void loadUrlFromTextBox() {
2913         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
2914         String unformattedUrlString = urlEditText.getText().toString().trim();
2915
2916         // Initialize the formatted URL string.
2917         String url = "";
2918
2919         // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
2920         if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
2921             // Load the entire content URL.
2922             url = unformattedUrlString;
2923         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2924                 unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
2925             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
2926             if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
2927                 unformattedUrlString = "https://" + unformattedUrlString;
2928             }
2929
2930             // Initialize `unformattedUrl`.
2931             URL unformattedUrl = null;
2932
2933             // Convert `unforma