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