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