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