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