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