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