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