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