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