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