]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Show the bottom app bar when opening a new tab. https://redmine.stoutner.com/issues/729
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
1 /*
2  * Copyright © 2015-2021 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.annotation.SuppressLint;
25 import android.app.Activity;
26 import android.app.Dialog;
27 import android.app.DownloadManager;
28 import android.app.SearchManager;
29 import android.content.ActivityNotFoundException;
30 import android.content.BroadcastReceiver;
31 import android.content.ClipData;
32 import android.content.ClipboardManager;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.SharedPreferences;
37 import android.content.pm.PackageManager;
38 import android.content.res.Configuration;
39 import android.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.BitmapFactory;
42 import android.graphics.Typeface;
43 import android.graphics.drawable.BitmapDrawable;
44 import android.graphics.drawable.Drawable;
45 import android.net.Uri;
46 import android.net.http.SslCertificate;
47 import android.net.http.SslError;
48 import android.os.AsyncTask;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Environment;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.preference.PreferenceManager;
55 import android.print.PrintDocumentAdapter;
56 import android.print.PrintManager;
57 import android.provider.DocumentsContract;
58 import android.text.Editable;
59 import android.text.Spanned;
60 import android.text.TextWatcher;
61 import android.text.style.ForegroundColorSpan;
62 import android.util.Patterns;
63 import android.util.TypedValue;
64 import android.view.ContextMenu;
65 import android.view.GestureDetector;
66 import android.view.KeyEvent;
67 import android.view.Menu;
68 import android.view.MenuItem;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.view.ViewGroup;
72 import android.view.WindowManager;
73 import android.view.inputmethod.InputMethodManager;
74 import android.webkit.CookieManager;
75 import android.webkit.HttpAuthHandler;
76 import android.webkit.SslErrorHandler;
77 import android.webkit.ValueCallback;
78 import android.webkit.WebBackForwardList;
79 import android.webkit.WebChromeClient;
80 import android.webkit.WebResourceResponse;
81 import android.webkit.WebSettings;
82 import android.webkit.WebStorage;
83 import android.webkit.WebView;
84 import android.webkit.WebViewClient;
85 import android.webkit.WebViewDatabase;
86 import android.widget.ArrayAdapter;
87 import android.widget.CheckBox;
88 import android.widget.CursorAdapter;
89 import android.widget.EditText;
90 import android.widget.FrameLayout;
91 import android.widget.ImageView;
92 import android.widget.LinearLayout;
93 import android.widget.ListView;
94 import android.widget.ProgressBar;
95 import android.widget.RadioButton;
96 import android.widget.RelativeLayout;
97 import android.widget.TextView;
98
99 import androidx.annotation.NonNull;
100 import androidx.appcompat.app.ActionBar;
101 import androidx.appcompat.app.ActionBarDrawerToggle;
102 import androidx.appcompat.app.AppCompatActivity;
103 import androidx.appcompat.app.AppCompatDelegate;
104 import androidx.appcompat.widget.Toolbar;
105 import androidx.coordinatorlayout.widget.CoordinatorLayout;
106 import androidx.core.content.res.ResourcesCompat;
107 import androidx.core.view.GravityCompat;
108 import androidx.drawerlayout.widget.DrawerLayout;
109 import androidx.fragment.app.DialogFragment;
110 import androidx.fragment.app.Fragment;
111 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
112 import androidx.viewpager.widget.ViewPager;
113 import androidx.webkit.WebSettingsCompat;
114 import androidx.webkit.WebViewFeature;
115
116 import com.google.android.material.appbar.AppBarLayout;
117 import com.google.android.material.floatingactionbutton.FloatingActionButton;
118 import com.google.android.material.navigation.NavigationView;
119 import com.google.android.material.snackbar.Snackbar;
120 import com.google.android.material.tabs.TabLayout;
121
122 import com.stoutner.privacybrowser.BuildConfig;
123 import com.stoutner.privacybrowser.R;
124 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
125 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
126 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
127 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
128 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
129 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
130 import com.stoutner.privacybrowser.definitions.PendingDialog;
131 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
132 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
133 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
134 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
135 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
136 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
137 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
138 import com.stoutner.privacybrowser.dialogs.OpenDialog;
139 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
140 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
141 import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
142 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
143 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
144 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
145 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
146 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
147 import com.stoutner.privacybrowser.helpers.AdHelper;
148 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
149 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
150 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
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.FileInputStream;
158 import java.io.FileOutputStream;
159 import java.io.IOException;
160 import java.io.InputStream;
161 import java.io.OutputStream;
162 import java.io.UnsupportedEncodingException;
163
164 import java.net.MalformedURLException;
165 import java.net.URL;
166 import java.net.URLDecoder;
167 import java.net.URLEncoder;
168
169 import java.text.NumberFormat;
170
171 import java.util.ArrayList;
172 import java.util.Calendar;
173 import java.util.Date;
174 import java.util.HashMap;
175 import java.util.HashSet;
176 import java.util.List;
177 import java.util.Map;
178 import java.util.Objects;
179 import java.util.Set;
180 import java.util.concurrent.ExecutorService;
181 import java.util.concurrent.Executors;
182
183 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
184         EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
185         PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, UrlHistoryDialog.NavigateHistoryListener,
186         WebViewTabFragment.NewTabListener {
187
188     // The executor service handles background tasks.  It is accessed from `ViewSourceActivity`.
189     public static ExecutorService executorService = Executors.newFixedThreadPool(4);
190
191     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
192     public static String orbotStatus = "unknown";
193
194     // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`.  It is also used in `onCreate()`, `onResume()`, and `addTab()`.
195     public static WebViewPagerAdapter webViewPagerAdapter;
196
197     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
198     public static boolean restartFromBookmarksActivity;
199
200     // Define the public static variables.
201     public static ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
202
203     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
204     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
205     public static String currentBookmarksFolder;
206
207     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
208     public final static int UNRECOGNIZED_USER_AGENT = -1;
209     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
210     public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
211     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
212     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
213     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
214
215     // Define the start activity for result request codes.  The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
216     private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
217     public final static int BROWSE_OPEN_REQUEST_CODE = 1;
218     public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 2;
219
220     // The proxy mode is public static so it can be accessed from `ProxyHelper()`.
221     // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
222     // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
223     public static String proxyMode = ProxyHelper.NONE;
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 variables.
238     @SuppressWarnings("rawtypes")
239     AsyncTask populateBlocklists;
240
241     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
242     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
243     private NestedScrollWebView currentWebView;
244
245     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
246     private final Map<String, String> customHeaders = new HashMap<>();
247
248     // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
249     private String searchURL;
250
251     // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
252     private ArrayList<List<String[]>> easyList;
253     private ArrayList<List<String[]>> easyPrivacy;
254     private ArrayList<List<String[]>> fanboysAnnoyanceList;
255     private ArrayList<List<String[]>> fanboysSocialList;
256     private ArrayList<List<String[]>> ultraList;
257     private ArrayList<List<String[]>> ultraPrivacy;
258
259     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
260     private ActionBarDrawerToggle actionBarDrawerToggle;
261
262     // The color spans are used in `onCreate()` and `highlightUrlText()`.
263     private ForegroundColorSpan redColorSpan;
264     private ForegroundColorSpan initialGrayColorSpan;
265     private ForegroundColorSpan finalGrayColorSpan;
266
267     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
268     // and `loadBookmarksFolder()`.
269     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
270
271     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
272     private Cursor bookmarksCursor;
273
274     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
275     private CursorAdapter bookmarksCursorAdapter;
276
277     // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
278     private String oldFolderNameString;
279
280     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
281     private ValueCallback<Uri[]> fileChooserCallback;
282
283     // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
284     private int appBarHeight;
285     private int defaultProgressViewStartOffset;
286     private int defaultProgressViewEndOffset;
287
288     // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
289     private boolean sanitizeGoogleAnalytics;
290     private boolean sanitizeFacebookClickIds;
291     private boolean sanitizeTwitterAmpRedirects;
292
293     // Declare the class variables
294     private BroadcastReceiver orbotStatusBroadcastReceiver;
295     private String webViewDefaultUserAgent;
296     private boolean incognitoModeEnabled;
297     private boolean fullScreenBrowsingModeEnabled;
298     private boolean inFullScreenBrowsingMode;
299     private boolean downloadWithExternalApp;
300     private boolean hideAppBar;
301     private boolean scrollAppBar;
302     private boolean bottomAppBar;
303     private boolean loadingNewIntent;
304     private boolean reapplyDomainSettingsOnRestart;
305     private boolean reapplyAppSettingsOnRestart;
306     private boolean displayingFullScreenVideo;
307     private boolean waitingForProxy;
308
309     // Define the class variables.
310     private long lastScrollUpdate = 0;
311
312     // Declare the class views.
313     private FrameLayout rootFrameLayout;
314     private DrawerLayout drawerLayout;
315     private RelativeLayout mainContentRelativeLayout;
316     private AppBarLayout appBarLayout;
317     private Toolbar toolbar;
318     private RelativeLayout urlRelativeLayout;
319     private EditText urlEditText;
320     private ActionBar actionBar;
321     private LinearLayout findOnPageLinearLayout;
322     private LinearLayout tabsLinearLayout;
323     private TabLayout tabLayout;
324     private SwipeRefreshLayout swipeRefreshLayout;
325     private ViewPager webViewPager;
326     private FrameLayout fullScreenVideoFrameLayout;
327
328     // Declare the class menus.
329     private Menu optionsMenu;
330
331     // Declare the class menu items.
332     private MenuItem navigationBackMenuItem;
333     private MenuItem navigationForwardMenuItem;
334     private MenuItem navigationHistoryMenuItem;
335     private MenuItem navigationRequestsMenuItem;
336     private MenuItem optionsPrivacyMenuItem;
337     private MenuItem optionsRefreshMenuItem;
338     private MenuItem optionsCookiesMenuItem;
339     private MenuItem optionsDomStorageMenuItem;
340     private MenuItem optionsSaveFormDataMenuItem;
341     private MenuItem optionsClearDataMenuItem;
342     private MenuItem optionsClearCookiesMenuItem;
343     private MenuItem optionsClearDomStorageMenuItem;
344     private MenuItem optionsClearFormDataMenuItem;
345     private MenuItem optionsBlocklistsMenuItem;
346     private MenuItem optionsEasyListMenuItem;
347     private MenuItem optionsEasyPrivacyMenuItem;
348     private MenuItem optionsFanboysAnnoyanceListMenuItem;
349     private MenuItem optionsFanboysSocialBlockingListMenuItem;
350     private MenuItem optionsUltraListMenuItem;
351     private MenuItem optionsUltraPrivacyMenuItem;
352     private MenuItem optionsBlockAllThirdPartyRequestsMenuItem;
353     private MenuItem optionsProxyMenuItem;
354     private MenuItem optionsProxyNoneMenuItem;
355     private MenuItem optionsProxyTorMenuItem;
356     private MenuItem optionsProxyI2pMenuItem;
357     private MenuItem optionsProxyCustomMenuItem;
358     private MenuItem optionsUserAgentMenuItem;
359     private MenuItem optionsUserAgentPrivacyBrowserMenuItem;
360     private MenuItem optionsUserAgentWebViewDefaultMenuItem;
361     private MenuItem optionsUserAgentFirefoxOnAndroidMenuItem;
362     private MenuItem optionsUserAgentChromeOnAndroidMenuItem;
363     private MenuItem optionsUserAgentSafariOnIosMenuItem;
364     private MenuItem optionsUserAgentFirefoxOnLinuxMenuItem;
365     private MenuItem optionsUserAgentChromiumOnLinuxMenuItem;
366     private MenuItem optionsUserAgentFirefoxOnWindowsMenuItem;
367     private MenuItem optionsUserAgentChromeOnWindowsMenuItem;
368     private MenuItem optionsUserAgentEdgeOnWindowsMenuItem;
369     private MenuItem optionsUserAgentInternetExplorerOnWindowsMenuItem;
370     private MenuItem optionsUserAgentSafariOnMacosMenuItem;
371     private MenuItem optionsUserAgentCustomMenuItem;
372     private MenuItem optionsSwipeToRefreshMenuItem;
373     private MenuItem optionsWideViewportMenuItem;
374     private MenuItem optionsDisplayImagesMenuItem;
375     private MenuItem optionsDarkWebViewMenuItem;
376     private MenuItem optionsFontSizeMenuItem;
377     private MenuItem optionsAddOrEditDomainMenuItem;
378
379     @Override
380     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
381     @SuppressLint("ClickableViewAccessibility")
382     protected void onCreate(Bundle savedInstanceState) {
383         // Run the default commands.
384         super.onCreate(savedInstanceState);
385
386         // Check to see if the activity has been restarted.
387         if (savedInstanceState != null) {
388             // Store the saved instance state variables.
389             savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
390             savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
391             savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
392             savedProxyMode = savedInstanceState.getString(PROXY_MODE);
393         }
394
395         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
396         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
397
398         // Get a handle for the shared preferences.
399         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
400
401         // Get the preferences.
402         String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
403         boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
404         bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
405
406         // Get the theme entry values string array.
407         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
408
409         // 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.
410         if (appTheme.equals(appThemeEntryValuesStringArray[1])) {  // The light theme is selected.
411             // Apply the light theme.
412             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
413         } else if (appTheme.equals(appThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
414             // Apply the dark theme.
415             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
416         } else {  // The system default theme is selected.
417             if (Build.VERSION.SDK_INT >= 28) {  // The system default theme is supported.
418                 // Follow the system default theme.
419                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
420             } else {  // The system default theme is not supported.
421                 // Follow the battery saver mode.
422                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
423             }
424         }
425
426         // Disable screenshots if not allowed.
427         if (!allowScreenshots) {
428             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
429         }
430
431         // 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.
432         if (Build.VERSION.SDK_INT >= 21) {
433             WebView.enableSlowWholeDocumentDraw();
434         }
435
436         // Set the theme.
437         setTheme(R.style.PrivacyBrowser);
438
439         // Set the content view.
440         if (bottomAppBar) {
441             setContentView(R.layout.main_framelayout_bottom_appbar);
442         } else {
443             setContentView(R.layout.main_framelayout_top_appbar);
444         }
445
446         // Get handles for the views.
447         rootFrameLayout = findViewById(R.id.root_framelayout);
448         drawerLayout = findViewById(R.id.drawerlayout);
449         mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
450         appBarLayout = findViewById(R.id.appbar_layout);
451         toolbar = findViewById(R.id.toolbar);
452         findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
453         tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
454         tabLayout = findViewById(R.id.tablayout);
455         swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
456         webViewPager = findViewById(R.id.webviewpager);
457         fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
458
459         // Get a handle for the navigation view.
460         NavigationView navigationView = findViewById(R.id.navigationview);
461
462         // Get a handle for the navigation menu.
463         Menu navigationMenu = navigationView.getMenu();
464
465         // Get handles for the navigation menu items.
466         navigationBackMenuItem = navigationMenu.findItem(R.id.back);
467         navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
468         navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
469         navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
470
471         // Listen for touches on the navigation menu.
472         navigationView.setNavigationItemSelectedListener(this);
473
474         // Get a handle for the app compat delegate.
475         AppCompatDelegate appCompatDelegate = getDelegate();
476
477         // Set the support action bar.
478         appCompatDelegate.setSupportActionBar(toolbar);
479
480         // Get a handle for the action bar.
481         actionBar = appCompatDelegate.getSupportActionBar();
482
483         // Remove the incorrect lint warning below that the action bar might be null.
484         assert actionBar != null;
485
486         // Add the custom layout, which shows the URL text bar.
487         actionBar.setCustomView(R.layout.url_app_bar);
488         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
489
490         // Get handles for the views in the URL app bar.
491         urlRelativeLayout = findViewById(R.id.url_relativelayout);
492         urlEditText = findViewById(R.id.url_edittext);
493
494         // Create the hamburger icon at the start of the AppBar.
495         actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
496
497         // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
498         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
499
500         // Initialize the web view pager adapter.
501         webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
502
503         // Set the pager adapter on the web view pager.
504         webViewPager.setAdapter(webViewPagerAdapter);
505
506         // Store up to 100 tabs in memory.
507         webViewPager.setOffscreenPageLimit(100);
508
509         // Initialize the app.
510         initializeApp();
511
512         // Apply the app settings from the shared preferences.
513         applyAppSettings();
514
515         // Populate the blocklists.
516         populateBlocklists = new PopulateBlocklists(this, this).execute();
517     }
518
519     @Override
520     protected void onNewIntent(Intent intent) {
521         // Run the default commands.
522         super.onNewIntent(intent);
523
524         // Replace the intent that started the app with this one.
525         setIntent(intent);
526
527         // Check to see if the app is being restarted from a saved state.
528         if (savedStateArrayList == null || savedStateArrayList.size() == 0) {  // The activity is not being restarted from a saved state.
529             // Get the information from the intent.
530             String intentAction = intent.getAction();
531             Uri intentUriData = intent.getData();
532             String intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT);
533
534             // Determine if this is a web search.
535             boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
536
537             // 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.
538             if (intentUriData != null || intentStringExtra != null || isWebSearch) {
539                 // Exit the full screen video if it is displayed.
540                 if (displayingFullScreenVideo) {
541                     // Exit full screen video mode.
542                     exitFullScreenVideo();
543
544                     // Reload the current WebView.  Otherwise, it can display entirely black.
545                     currentWebView.reload();
546                 }
547
548                 // Get the shared preferences.
549                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
550
551                 // Create a URL string.
552                 String url;
553
554                 // If the intent action is a web search, perform the search.
555                 if (isWebSearch) {  // The intent is a web search.
556                     // Create an encoded URL string.
557                     String encodedUrlString;
558
559                     // Sanitize the search input and convert it to a search.
560                     try {
561                         encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
562                     } catch (UnsupportedEncodingException exception) {
563                         encodedUrlString = "";
564                     }
565
566                     // Add the base search URL.
567                     url = searchURL + encodedUrlString;
568                 } else if (intentUriData != null) {  // The intent contains a URL formatted as a URI.
569                     // Set the intent data as the URL.
570                     url = intentUriData.toString();
571                 } else {  // The intent contains a string, which might be a URL.
572                     // Set the intent string as the URL.
573                     url = intentStringExtra;
574                 }
575
576                 // Add a new tab if specified in the preferences.
577                 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
578                     // Set the loading new intent flag.
579                     loadingNewIntent = true;
580
581                     // Add a new tab.
582                     addNewTab(url, true);
583                 } else {  // Load the URL in the current tab.
584                     // Make it so.
585                     loadUrl(currentWebView, url);
586                 }
587
588                 // Close the navigation drawer if it is open.
589                 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
590                     drawerLayout.closeDrawer(GravityCompat.START);
591                 }
592
593                 // Close the bookmarks drawer if it is open.
594                 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
595                     drawerLayout.closeDrawer(GravityCompat.END);
596                 }
597             }
598         }
599     }
600
601     @Override
602     public void onRestart() {
603         // Run the default commands.
604         super.onRestart();
605
606         // Apply the app settings if returning from the Settings activity.
607         if (reapplyAppSettingsOnRestart) {
608             // Reset the reapply app settings on restart tracker.
609             reapplyAppSettingsOnRestart = false;
610
611             // Apply the app settings.
612             applyAppSettings();
613         }
614
615         // Apply the domain settings if returning from the settings or domains activity.
616         if (reapplyDomainSettingsOnRestart) {
617             // Reset the reapply domain settings on restart tracker.
618             reapplyDomainSettingsOnRestart = false;
619
620             // Reapply the domain settings for each tab.
621             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
622                 // Get the WebView tab fragment.
623                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
624
625                 // Get the fragment view.
626                 View fragmentView = webViewTabFragment.getView();
627
628                 // Only reload the WebViews if they exist.
629                 if (fragmentView != null) {
630                     // Get the nested scroll WebView from the tab fragment.
631                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
632
633                     // Reset the current domain name so the domain settings will be reapplied.
634                     nestedScrollWebView.resetCurrentDomainName();
635
636                     // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
637                     if (nestedScrollWebView.getUrl() != null) {
638                         applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true, false);
639                     }
640                 }
641             }
642         }
643
644         // Update the bookmarks drawer if returning from the Bookmarks activity.
645         if (restartFromBookmarksActivity) {
646             // Close the bookmarks drawer.
647             drawerLayout.closeDrawer(GravityCompat.END);
648
649             // Reload the bookmarks drawer.
650             loadBookmarksFolder();
651
652             // Reset `restartFromBookmarksActivity`.
653             restartFromBookmarksActivity = false;
654         }
655
656         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
657         updatePrivacyIcons(true);
658     }
659
660     // `onStart()` runs after `onCreate()` or `onRestart()`.  This is used instead of `onResume()` so the commands aren't called every time the screen is partially hidden.
661     @Override
662     public void onStart() {
663         // Run the default commands.
664         super.onStart();
665
666         // Resume any WebViews.
667         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
668             // Get the WebView tab fragment.
669             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
670
671             // Get the fragment view.
672             View fragmentView = webViewTabFragment.getView();
673
674             // Only resume the WebViews if they exist (they won't when the app is first created).
675             if (fragmentView != null) {
676                 // Get the nested scroll WebView from the tab fragment.
677                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
678
679                 // Resume the nested scroll WebView.
680                 nestedScrollWebView.onResume();
681             }
682         }
683
684         // Resume the nested scroll WebView JavaScript timers.  This is a global command that resumes JavaScript timers on all WebViews.
685         if (currentWebView != null) {
686             currentWebView.resumeTimers();
687         }
688
689         // Reapply the proxy settings if the system is using a proxy.  This redisplays the appropriate alert dialog.
690         if (!proxyMode.equals(ProxyHelper.NONE)) {
691             applyProxy(false);
692         }
693
694         // Reapply any system UI flags and the ad in the free flavor.
695         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {  // The system is displaying a website or a video in full screen mode.
696             /* Hide the system bars.
697              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
698              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
699              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
700              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
701              */
702             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
703                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
704         } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // The system in not in full screen mode.
705             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
706             View adView = findViewById(R.id.adview);
707
708             // Resume the ad.
709             AdHelper.resumeAd(adView);
710         }
711
712         // Show any pending dialogs.
713         for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
714             // Get the pending dialog from the array list.
715             PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
716
717             // Show the pending dialog.
718             pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
719         }
720
721         // Clear the pending dialogs array list.
722         pendingDialogsArrayList.clear();
723     }
724
725     // `onStop()` runs after `onPause()`.  It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
726     @Override
727     public void onStop() {
728         // Run the default commands.
729         super.onStop();
730
731         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
732             // Get the WebView tab fragment.
733             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
734
735             // Get the fragment view.
736             View fragmentView = webViewTabFragment.getView();
737
738             // Only pause the WebViews if they exist (they won't when the app is first created).
739             if (fragmentView != null) {
740                 // Get the nested scroll WebView from the tab fragment.
741                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
742
743                 // Pause the nested scroll WebView.
744                 nestedScrollWebView.onPause();
745             }
746         }
747
748         // Pause the WebView JavaScript timers.  This is a global command that pauses JavaScript on all WebViews.
749         if (currentWebView != null) {
750             currentWebView.pauseTimers();
751         }
752
753         // Pause the ad or it will continue to consume resources in the background on the free flavor.
754         if (BuildConfig.FLAVOR.contentEquals("free")) {
755             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
756             View adView = findViewById(R.id.adview);
757
758             // Pause the ad.
759             AdHelper.pauseAd(adView);
760         }
761     }
762
763     @Override
764     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
765         // Run the default commands.
766         super.onSaveInstanceState(savedInstanceState);
767
768         // Create the saved state array lists.
769         ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
770         ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
771
772         // Get the URLs from each tab.
773         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
774             // Get the WebView tab fragment.
775             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
776
777             // Get the fragment view.
778             View fragmentView = webViewTabFragment.getView();
779
780             if (fragmentView != null) {
781                 // Get the nested scroll WebView from the tab fragment.
782                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
783
784                 // Create saved state bundle.
785                 Bundle savedStateBundle = new Bundle();
786
787                 // Get the current states.
788                 nestedScrollWebView.saveState(savedStateBundle);
789                 Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
790
791                 // Store the saved states in the array lists.
792                 savedStateArrayList.add(savedStateBundle);
793                 savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
794             }
795         }
796
797         // Get the current tab position.
798         int currentTabPosition = tabLayout.getSelectedTabPosition();
799
800         // Store the saved states in the bundle.
801         savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
802         savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
803         savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
804         savedInstanceState.putString(PROXY_MODE, proxyMode);
805     }
806
807     @Override
808     public void onDestroy() {
809         // Unregister the orbot status broadcast receiver if it exists.
810         if (orbotStatusBroadcastReceiver != null) {
811             this.unregisterReceiver(orbotStatusBroadcastReceiver);
812         }
813
814         // Close the bookmarks cursor if it exists.
815         if (bookmarksCursor != null) {
816             bookmarksCursor.close();
817         }
818
819         // Close the bookmarks database if it exists.
820         if (bookmarksDatabaseHelper != null) {
821             bookmarksDatabaseHelper.close();
822         }
823
824         // Stop populating the blocklists if the AsyncTask is running in the background.
825         if (populateBlocklists != null) {
826             populateBlocklists.cancel(true);
827         }
828
829         // Run the default commands.
830         super.onDestroy();
831     }
832
833     @Override
834     public boolean onCreateOptionsMenu(Menu menu) {
835         // Inflate the menu.  This adds items to the action bar if it is present.
836         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
837
838         // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
839         optionsMenu = menu;
840
841         // Get handles for the class menu items.
842         optionsPrivacyMenuItem = menu.findItem(R.id.javascript);
843         optionsRefreshMenuItem = menu.findItem(R.id.refresh);
844         optionsCookiesMenuItem = menu.findItem(R.id.cookies);
845         optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage);
846         optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data);  // Form data can be removed once the minimum API >= 26.
847         optionsClearDataMenuItem = menu.findItem(R.id.clear_data);
848         optionsClearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
849         optionsClearDomStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
850         optionsClearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
851         optionsBlocklistsMenuItem = menu.findItem(R.id.blocklists);
852         optionsEasyListMenuItem = menu.findItem(R.id.easylist);
853         optionsEasyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
854         optionsFanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
855         optionsFanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
856         optionsUltraListMenuItem = menu.findItem(R.id.ultralist);
857         optionsUltraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
858         optionsBlockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
859         optionsProxyMenuItem = menu.findItem(R.id.proxy);
860         optionsProxyNoneMenuItem = menu.findItem(R.id.proxy_none);
861         optionsProxyTorMenuItem = menu.findItem(R.id.proxy_tor);
862         optionsProxyI2pMenuItem = menu.findItem(R.id.proxy_i2p);
863         optionsProxyCustomMenuItem = menu.findItem(R.id.proxy_custom);
864         optionsUserAgentMenuItem = menu.findItem(R.id.user_agent);
865         optionsUserAgentPrivacyBrowserMenuItem = menu.findItem(R.id.user_agent_privacy_browser);
866         optionsUserAgentWebViewDefaultMenuItem = menu.findItem(R.id.user_agent_webview_default);
867         optionsUserAgentFirefoxOnAndroidMenuItem = menu.findItem(R.id.user_agent_firefox_on_android);
868         optionsUserAgentChromeOnAndroidMenuItem = menu.findItem(R.id.user_agent_chrome_on_android);
869         optionsUserAgentSafariOnIosMenuItem = menu.findItem(R.id.user_agent_safari_on_ios);
870         optionsUserAgentFirefoxOnLinuxMenuItem = menu.findItem(R.id.user_agent_firefox_on_linux);
871         optionsUserAgentChromiumOnLinuxMenuItem = menu.findItem(R.id.user_agent_chromium_on_linux);
872         optionsUserAgentFirefoxOnWindowsMenuItem = menu.findItem(R.id.user_agent_firefox_on_windows);
873         optionsUserAgentChromeOnWindowsMenuItem = menu.findItem(R.id.user_agent_chrome_on_windows);
874         optionsUserAgentEdgeOnWindowsMenuItem = menu.findItem(R.id.user_agent_edge_on_windows);
875         optionsUserAgentInternetExplorerOnWindowsMenuItem = menu.findItem(R.id.user_agent_internet_explorer_on_windows);
876         optionsUserAgentSafariOnMacosMenuItem = menu.findItem(R.id.user_agent_safari_on_macos);
877         optionsUserAgentCustomMenuItem = menu.findItem(R.id.user_agent_custom);
878         optionsSwipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
879         optionsWideViewportMenuItem = menu.findItem(R.id.wide_viewport);
880         optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images);
881         optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview);
882         optionsFontSizeMenuItem = menu.findItem(R.id.font_size);
883         optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain);
884
885         // Get handles for the method menu items.
886         MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks);
887         MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
888
889         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
890         updatePrivacyIcons(false);
891
892         // Only display the form data menu items if the API < 26.
893         optionsSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
894         optionsClearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
895
896         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
897         optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
898
899         // Only display the dark WebView menu item if API >= 21.
900         optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
901
902         // Only show Ad Consent if this is the free flavor.
903         adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
904
905         // Get the shared preferences.
906         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
907
908         // Get the dark theme and app bar preferences.
909         boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
910
911         // 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.
912         if (displayAdditionalAppBarIcons) {
913             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
914             bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
915             optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
916         } else { //Do not display the additional icons.
917             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
918             bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
919             optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
920         }
921
922         // Replace Refresh with Stop if a URL is already loading.
923         if (currentWebView != null && currentWebView.getProgress() != 100) {
924             // Set the title.
925             optionsRefreshMenuItem.setTitle(R.string.stop);
926
927             // Set the icon if it is displayed in the app bar.
928             if (displayAdditionalAppBarIcons) {
929                 // Get the current theme status.
930                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
931
932                 // Set the icon according to the current theme status.
933                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
934                     optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
935                 } else {
936                     optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
937                 }
938             }
939         }
940
941         // Done.
942         return true;
943     }
944
945     @Override
946     public boolean onPrepareOptionsMenu(Menu menu) {
947         // Get a handle for the cookie manager.
948         CookieManager cookieManager = CookieManager.getInstance();
949
950         // Initialize the current user agent string and the font size.
951         String currentUserAgent = getString(R.string.user_agent_privacy_browser);
952         int fontSize = 100;
953
954         // 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.
955         if (currentWebView != null) {
956             // Set the add or edit domain text.
957             if (currentWebView.getDomainSettingsApplied()) {
958                 optionsAddOrEditDomainMenuItem.setTitle(R.string.edit_domain_settings);
959             } else {
960                 optionsAddOrEditDomainMenuItem.setTitle(R.string.add_domain_settings);
961             }
962
963             // Get the current user agent from the WebView.
964             currentUserAgent = currentWebView.getSettings().getUserAgentString();
965
966             // Get the current font size from the
967             fontSize = currentWebView.getSettings().getTextZoom();
968
969             // Set the status of the menu item checkboxes.
970             optionsDomStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
971             optionsSaveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
972             optionsEasyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
973             optionsEasyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
974             optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
975             optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
976             optionsUltraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
977             optionsUltraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
978             optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
979             optionsSwipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
980             optionsWideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
981             optionsDisplayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
982
983             // Initialize the display names for the blocklists with the number of blocked requests.
984             optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
985             optionsEasyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
986             optionsEasyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
987             optionsFanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
988             optionsFanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
989             optionsUltraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
990             optionsUltraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
991             optionsBlockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
992
993             // Enable DOM Storage if JavaScript is enabled.
994             optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
995
996             // Set the checkbox status for dark WebView if the WebView supports it.
997             if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
998                 optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
999             }
1000         }
1001
1002         // Set the cookies menu item checked status.
1003         optionsCookiesMenuItem.setChecked(cookieManager.acceptCookie());
1004
1005         // Enable Clear Cookies if there are any.
1006         optionsClearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
1007
1008         // 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`.
1009         String privateDataDirectoryString = getApplicationInfo().dataDir;
1010
1011         // Get a count of the number of files in the Local Storage directory.
1012         File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
1013         int localStorageDirectoryNumberOfFiles = 0;
1014         if (localStorageDirectory.exists()) {
1015             // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
1016             localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
1017         }
1018
1019         // Get a count of the number of files in the IndexedDB directory.
1020         File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
1021         int indexedDBDirectoryNumberOfFiles = 0;
1022         if (indexedDBDirectory.exists()) {
1023             // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
1024             indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
1025         }
1026
1027         // Enable Clear DOM Storage if there is any.
1028         optionsClearDomStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
1029
1030         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
1031         if (Build.VERSION.SDK_INT < 26) {
1032             // Get the WebView database.
1033             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1034
1035             // Enable the clear form data menu item if there is anything to clear.
1036             optionsClearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
1037         }
1038
1039         // Enable Clear Data if any of the submenu items are enabled.
1040         optionsClearDataMenuItem.setEnabled(optionsClearCookiesMenuItem.isEnabled() || optionsClearDomStorageMenuItem.isEnabled() || optionsClearFormDataMenuItem.isEnabled());
1041
1042         // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
1043         optionsFanboysSocialBlockingListMenuItem.setEnabled(!optionsFanboysAnnoyanceListMenuItem.isChecked());
1044
1045         // Set the proxy title and check the applied proxy.
1046         switch (proxyMode) {
1047             case ProxyHelper.NONE:
1048                 // Set the proxy title.
1049                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
1050
1051                 // Check the proxy None radio button.
1052                 optionsProxyNoneMenuItem.setChecked(true);
1053                 break;
1054
1055             case ProxyHelper.TOR:
1056                 // Set the proxy title.
1057                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
1058
1059                 // Check the proxy Tor radio button.
1060                 optionsProxyTorMenuItem.setChecked(true);
1061                 break;
1062
1063             case ProxyHelper.I2P:
1064                 // Set the proxy title.
1065                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
1066
1067                 // Check the proxy I2P radio button.
1068                 optionsProxyI2pMenuItem.setChecked(true);
1069                 break;
1070
1071             case ProxyHelper.CUSTOM:
1072                 // Set the proxy title.
1073                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
1074
1075                 // Check the proxy Custom radio button.
1076                 optionsProxyCustomMenuItem.setChecked(true);
1077                 break;
1078         }
1079
1080         // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
1081         if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
1082             // Update the user agent menu item title.
1083             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
1084
1085             // Select the Privacy Browser radio box.
1086             optionsUserAgentPrivacyBrowserMenuItem.setChecked(true);
1087         } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
1088             // Update the user agent menu item title.
1089             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
1090
1091             // Select the WebView Default radio box.
1092             optionsUserAgentWebViewDefaultMenuItem.setChecked(true);
1093         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
1094             // Update the user agent menu item title.
1095             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
1096
1097             // Select the Firefox on Android radio box.
1098             optionsUserAgentFirefoxOnAndroidMenuItem.setChecked(true);
1099         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
1100             // Update the user agent menu item title.
1101             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
1102
1103             // Select the Chrome on Android radio box.
1104             optionsUserAgentChromeOnAndroidMenuItem.setChecked(true);
1105         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
1106             // Update the user agent menu item title.
1107             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
1108
1109             // Select the Safari on iOS radio box.
1110             optionsUserAgentSafariOnIosMenuItem.setChecked(true);
1111         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
1112             // Update the user agent menu item title.
1113             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
1114
1115             // Select the Firefox on Linux radio box.
1116             optionsUserAgentFirefoxOnLinuxMenuItem.setChecked(true);
1117         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
1118             // Update the user agent menu item title.
1119             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
1120
1121             // Select the Chromium on Linux radio box.
1122             optionsUserAgentChromiumOnLinuxMenuItem.setChecked(true);
1123         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
1124             // Update the user agent menu item title.
1125             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
1126
1127             // Select the Firefox on Windows radio box.
1128             optionsUserAgentFirefoxOnWindowsMenuItem.setChecked(true);
1129         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
1130             // Update the user agent menu item title.
1131             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
1132
1133             // Select the Chrome on Windows radio box.
1134             optionsUserAgentChromeOnWindowsMenuItem.setChecked(true);
1135         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
1136             // Update the user agent menu item title.
1137             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
1138
1139             // Select the Edge on Windows radio box.
1140             optionsUserAgentEdgeOnWindowsMenuItem.setChecked(true);
1141         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
1142             // Update the user agent menu item title.
1143             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
1144
1145             // Select the Internet on Windows radio box.
1146             optionsUserAgentInternetExplorerOnWindowsMenuItem.setChecked(true);
1147         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
1148             // Update the user agent menu item title.
1149             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
1150
1151             // Select the Safari on macOS radio box.
1152             optionsUserAgentSafariOnMacosMenuItem.setChecked(true);
1153         } else {  // Custom user agent.
1154             // Update the user agent menu item title.
1155             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
1156
1157             // Select the Custom radio box.
1158             optionsUserAgentCustomMenuItem.setChecked(true);
1159         }
1160
1161         // Set the font size title.
1162         optionsFontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
1163
1164         // Run all the other default commands.
1165         super.onPrepareOptionsMenu(menu);
1166
1167         // Display the menu.
1168         return true;
1169     }
1170
1171     @Override
1172     public boolean onOptionsItemSelected(MenuItem menuItem) {
1173         // Get a handle for the shared preferences.
1174         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1175
1176         // Get a handle for the cookie manager.
1177         CookieManager cookieManager = CookieManager.getInstance();
1178
1179         // Get the selected menu item ID.
1180         int menuItemId = menuItem.getItemId();
1181
1182         // Run the commands that correlate to the selected menu item.
1183         if (menuItemId == R.id.javascript) {  // JavaScript.
1184             // Toggle the JavaScript status.
1185             currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1186
1187             // Update the privacy icon.
1188             updatePrivacyIcons(true);
1189
1190             // Display a `Snackbar`.
1191             if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
1192                 Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1193             } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
1194                 Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1195             } else {  // Privacy mode.
1196                 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1197             }
1198
1199             // Reload the current WebView.
1200             currentWebView.reload();
1201
1202             // Consume the event.
1203             return true;
1204         } else if (menuItemId == R.id.refresh) {  // Refresh.
1205             // Run the command that correlates to the current status of the menu item.
1206             if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
1207                 // Reload the current WebView.
1208                 currentWebView.reload();
1209             } else {  // The stop button was pushed.
1210                 // Stop the loading of the WebView.
1211                 currentWebView.stopLoading();
1212             }
1213
1214             // Consume the event.
1215             return true;
1216         } else if (menuItemId == R.id.bookmarks) {  // Bookmarks.
1217             // Open the bookmarks drawer.
1218             drawerLayout.openDrawer(GravityCompat.END);
1219
1220             // Consume the event.
1221             return true;
1222         } else if (menuItemId == R.id.cookies) {  // Cookies.
1223             // Switch the first-party cookie status.
1224             cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1225
1226             // Store the cookie status.
1227             currentWebView.setAcceptCookies(cookieManager.acceptCookie());
1228
1229             // Update the menu checkbox.
1230             menuItem.setChecked(cookieManager.acceptCookie());
1231
1232             // Update the privacy icon.
1233             updatePrivacyIcons(true);
1234
1235             // Display a snackbar.
1236             if (cookieManager.acceptCookie()) {  // Cookies are enabled.
1237                 Snackbar.make(webViewPager, R.string.cookies_enabled, Snackbar.LENGTH_SHORT).show();
1238             } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
1239                 Snackbar.make(webViewPager, R.string.cookies_disabled, Snackbar.LENGTH_SHORT).show();
1240             } else {  // Privacy mode.
1241                 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1242             }
1243
1244             // Reload the current WebView.
1245             currentWebView.reload();
1246
1247             // Consume the event.
1248             return true;
1249         } else if (menuItemId == R.id.dom_storage) {  // DOM storage.
1250             // Toggle the status of domStorageEnabled.
1251             currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1252
1253             // Update the menu checkbox.
1254             menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1255
1256             // Update the privacy icon.
1257             updatePrivacyIcons(true);
1258
1259             // Display a snackbar.
1260             if (currentWebView.getSettings().getDomStorageEnabled()) {
1261                 Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1262             } else {
1263                 Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1264             }
1265
1266             // Reload the current WebView.
1267             currentWebView.reload();
1268
1269             // Consume the event.
1270             return true;
1271         } else if (menuItemId == R.id.save_form_data) {  // Form data.  This can be removed once the minimum API >= 26.
1272             // Switch the status of saveFormDataEnabled.
1273             currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1274
1275             // Update the menu checkbox.
1276             menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1277
1278             // Display a snackbar.
1279             if (currentWebView.getSettings().getSaveFormData()) {
1280                 Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1281             } else {
1282                 Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1283             }
1284
1285             // Update the privacy icon.
1286             updatePrivacyIcons(true);
1287
1288             // Reload the current WebView.
1289             currentWebView.reload();
1290
1291             // Consume the event.
1292             return true;
1293         } else if (menuItemId == R.id.clear_cookies) {  // Clear cookies.
1294             // Create a snackbar.
1295             Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1296                     .setAction(R.string.undo, v -> {
1297                         // Do nothing because everything will be handled by `onDismissed()` below.
1298                     })
1299                     .addCallback(new Snackbar.Callback() {
1300                         @Override
1301                         public void onDismissed(Snackbar snackbar, int event) {
1302                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1303                                 // Delete the cookies, which command varies by SDK.
1304                                 if (Build.VERSION.SDK_INT < 21) {
1305                                     cookieManager.removeAllCookie();
1306                                 } else {
1307                                     cookieManager.removeAllCookies(null);
1308                                 }
1309                             }
1310                         }
1311                     })
1312                     .show();
1313
1314             // Consume the event.
1315             return true;
1316         } else if (menuItemId == R.id.clear_dom_storage) {  // Clear DOM storage.
1317             // Create a snackbar.
1318             Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1319                     .setAction(R.string.undo, v -> {
1320                         // Do nothing because everything will be handled by `onDismissed()` below.
1321                     })
1322                     .addCallback(new Snackbar.Callback() {
1323                         @Override
1324                         public void onDismissed(Snackbar snackbar, int event) {
1325                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1326                                 // Delete the DOM Storage.
1327                                 WebStorage webStorage = WebStorage.getInstance();
1328                                 webStorage.deleteAllData();
1329
1330                                 // Initialize a handler to manually delete the DOM storage files and directories.
1331                                 Handler deleteDomStorageHandler = new Handler();
1332
1333                                 // Setup a runnable to manually delete the DOM storage files and directories.
1334                                 Runnable deleteDomStorageRunnable = () -> {
1335                                     try {
1336                                         // Get a handle for the runtime.
1337                                         Runtime runtime = Runtime.getRuntime();
1338
1339                                         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1340                                         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1341                                         String privateDataDirectoryString = getApplicationInfo().dataDir;
1342
1343                                         // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1344                                         Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1345
1346                                         // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1347                                         Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1348                                         Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1349                                         Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1350                                         Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1351
1352                                         // Wait for the processes to finish.
1353                                         deleteLocalStorageProcess.waitFor();
1354                                         deleteIndexProcess.waitFor();
1355                                         deleteQuotaManagerProcess.waitFor();
1356                                         deleteQuotaManagerJournalProcess.waitFor();
1357                                         deleteDatabasesProcess.waitFor();
1358                                     } catch (Exception exception) {
1359                                         // Do nothing if an error is thrown.
1360                                     }
1361                                 };
1362
1363                                 // Manually delete the DOM storage files after 200 milliseconds.
1364                                 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1365                             }
1366                         }
1367                     })
1368                     .show();
1369
1370             // Consume the event.
1371             return true;
1372         } else if (menuItemId == R.id.clear_form_data) {  // Clear form data.  This can be remove once the minimum API >= 26.
1373             // Create a snackbar.
1374             Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1375                     .setAction(R.string.undo, v -> {
1376                         // Do nothing because everything will be handled by `onDismissed()` below.
1377                     })
1378                     .addCallback(new Snackbar.Callback() {
1379                         @Override
1380                         public void onDismissed(Snackbar snackbar, int event) {
1381                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1382                                 // Get a handle for the webView database.
1383                                 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1384
1385                                 // Delete the form data.
1386                                 webViewDatabase.clearFormData();
1387                             }
1388                         }
1389                     })
1390                     .show();
1391
1392             // Consume the event.
1393             return true;
1394         } else if (menuItemId == R.id.easylist) {  // EasyList.
1395             // Toggle the EasyList status.
1396             currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1397
1398             // Update the menu checkbox.
1399             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1400
1401             // Reload the current WebView.
1402             currentWebView.reload();
1403
1404             // Consume the event.
1405             return true;
1406         } else if (menuItemId == R.id.easyprivacy) {  // EasyPrivacy.
1407             // Toggle the EasyPrivacy status.
1408             currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1409
1410             // Update the menu checkbox.
1411             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1412
1413             // Reload the current WebView.
1414             currentWebView.reload();
1415
1416             // Consume the event.
1417             return true;
1418         } else if (menuItemId == R.id.fanboys_annoyance_list) {  // Fanboy's Annoyance List.
1419             // Toggle Fanboy's Annoyance List status.
1420             currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1421
1422             // Update the menu checkbox.
1423             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1424
1425             // Update the status of Fanboy's Social Blocking List.
1426             optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1427
1428             // Reload the current WebView.
1429             currentWebView.reload();
1430
1431             // Consume the event.
1432             return true;
1433         } else if (menuItemId == R.id.fanboys_social_blocking_list) {  // Fanboy's Social Blocking List.
1434             // Toggle Fanboy's Social Blocking List status.
1435             currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1436
1437             // Update the menu checkbox.
1438             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1439
1440             // Reload the current WebView.
1441             currentWebView.reload();
1442
1443             // Consume the event.
1444             return true;
1445         } else if (menuItemId == R.id.ultralist) {  // UltraList.
1446             // Toggle the UltraList status.
1447             currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1448
1449             // Update the menu checkbox.
1450             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1451
1452             // Reload the current WebView.
1453             currentWebView.reload();
1454
1455             // Consume the event.
1456             return true;
1457         } else if (menuItemId == R.id.ultraprivacy) {  // UltraPrivacy.
1458             // Toggle the UltraPrivacy status.
1459             currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1460
1461             // Update the menu checkbox.
1462             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1463
1464             // Reload the current WebView.
1465             currentWebView.reload();
1466
1467             // Consume the event.
1468             return true;
1469         } else if (menuItemId == R.id.block_all_third_party_requests) {  // Block all third-party requests.
1470             //Toggle the third-party requests blocker status.
1471             currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1472
1473             // Update the menu checkbox.
1474             menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1475
1476             // Reload the current WebView.
1477             currentWebView.reload();
1478
1479             // Consume the event.
1480             return true;
1481         } else if (menuItemId == R.id.proxy_none) {  // Proxy - None.
1482             // Update the proxy mode.
1483             proxyMode = ProxyHelper.NONE;
1484
1485             // Apply the proxy mode.
1486             applyProxy(true);
1487
1488             // Consume the event.
1489             return true;
1490         } else if (menuItemId == R.id.proxy_tor) {  // Proxy - Tor.
1491             // Update the proxy mode.
1492             proxyMode = ProxyHelper.TOR;
1493
1494             // Apply the proxy mode.
1495             applyProxy(true);
1496
1497             // Consume the event.
1498             return true;
1499         } else if (menuItemId == R.id.proxy_i2p) {  // Proxy - I2P.
1500             // Update the proxy mode.
1501             proxyMode = ProxyHelper.I2P;
1502
1503             // Apply the proxy mode.
1504             applyProxy(true);
1505
1506             // Consume the event.
1507             return true;
1508         } else if (menuItemId == R.id.proxy_custom) {  // Proxy - Custom.
1509             // Update the proxy mode.
1510             proxyMode = ProxyHelper.CUSTOM;
1511
1512             // Apply the proxy mode.
1513             applyProxy(true);
1514
1515             // Consume the event.
1516             return true;
1517         } else if (menuItemId == R.id.user_agent_privacy_browser) {  // User Agent - Privacy Browser.
1518             // Update the user agent.
1519             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1520
1521             // Reload the current WebView.
1522             currentWebView.reload();
1523
1524             // Consume the event.
1525             return true;
1526         } else if (menuItemId == R.id.user_agent_webview_default) {  // User Agent - WebView Default.
1527             // Update the user agent.
1528             currentWebView.getSettings().setUserAgentString("");
1529
1530             // Reload the current WebView.
1531             currentWebView.reload();
1532
1533             // Consume the event.
1534             return true;
1535         } else if (menuItemId == R.id.user_agent_firefox_on_android) {  // User Agent - Firefox on Android.
1536             // Update the user agent.
1537             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1538
1539             // Reload the current WebView.
1540             currentWebView.reload();
1541
1542             // Consume the event.
1543             return true;
1544         } else if (menuItemId == R.id.user_agent_chrome_on_android) {  // User Agent - Chrome on Android.
1545             // Update the user agent.
1546             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1547
1548             // Reload the current WebView.
1549             currentWebView.reload();
1550
1551             // Consume the event.
1552             return true;
1553         } else if (menuItemId == R.id.user_agent_safari_on_ios) {  // User Agent - Safari on iOS.
1554             // Update the user agent.
1555             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1556
1557             // Reload the current WebView.
1558             currentWebView.reload();
1559
1560             // Consume the event.
1561             return true;
1562         } else if (menuItemId == R.id.user_agent_firefox_on_linux) {  // User Agent - Firefox on Linux.
1563             // Update the user agent.
1564             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1565
1566             // Reload the current WebView.
1567             currentWebView.reload();
1568
1569             // Consume the event.
1570             return true;
1571         } else if (menuItemId == R.id.user_agent_chromium_on_linux) {  // User Agent - Chromium on Linux.
1572             // Update the user agent.
1573             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1574
1575             // Reload the current WebView.
1576             currentWebView.reload();
1577
1578             // Consume the event.
1579             return true;
1580         } else if (menuItemId == R.id.user_agent_firefox_on_windows) {  // User Agent - Firefox on Windows.
1581             // Update the user agent.
1582             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1583
1584             // Reload the current WebView.
1585             currentWebView.reload();
1586
1587             // Consume the event.
1588             return true;
1589         } else if (menuItemId == R.id.user_agent_chrome_on_windows) {  // User Agent - Chrome on Windows.
1590             // Update the user agent.
1591             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1592
1593             // Reload the current WebView.
1594             currentWebView.reload();
1595
1596             // Consume the event.
1597             return true;
1598         } else if (menuItemId == R.id.user_agent_edge_on_windows) {  // User Agent - Edge on Windows.
1599             // Update the user agent.
1600             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1601
1602             // Reload the current WebView.
1603             currentWebView.reload();
1604
1605             // Consume the event.
1606             return true;
1607         } else if (menuItemId == R.id.user_agent_internet_explorer_on_windows) {  // User Agent - Internet Explorer on Windows.
1608             // Update the user agent.
1609             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1610
1611             // Reload the current WebView.
1612             currentWebView.reload();
1613
1614             // Consume the event.
1615             return true;
1616         } else if (menuItemId == R.id.user_agent_safari_on_macos) {  // User Agent - Safari on macOS.
1617             // Update the user agent.
1618             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1619
1620             // Reload the current WebView.
1621             currentWebView.reload();
1622
1623             // Consume the event.
1624             return true;
1625         } else if (menuItemId == R.id.user_agent_custom) {  // User Agent - Custom.
1626             // Update the user agent.
1627             currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1628
1629             // Reload the current WebView.
1630             currentWebView.reload();
1631
1632             // Consume the event.
1633             return true;
1634         } else if (menuItemId == R.id.font_size) {  // Font size.
1635             // Instantiate the font size dialog.
1636             DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
1637
1638             // Show the font size dialog.
1639             fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
1640
1641             // Consume the event.
1642             return true;
1643         } else if (menuItemId == R.id.swipe_to_refresh) {  // Swipe to refresh.
1644             // Toggle the stored status of swipe to refresh.
1645             currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1646
1647             // Update the swipe refresh layout.
1648             if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
1649                 // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
1650                 swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
1651             } else {  // Swipe to refresh is disabled.
1652                 // Disable the swipe refresh layout.
1653                 swipeRefreshLayout.setEnabled(false);
1654             }
1655
1656             // Consume the event.
1657             return true;
1658         } else if (menuItemId == R.id.wide_viewport) {  // Wide viewport.
1659             // Toggle the viewport.
1660             currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1661
1662             // Consume the event.
1663             return true;
1664         } else if (menuItemId == R.id.display_images) {  // Display images.
1665             // Toggle the displaying of images.
1666             if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
1667                 // Disable loading of images.
1668                 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1669
1670                 // Reload the website to remove existing images.
1671                 currentWebView.reload();
1672             } else {  // Images are not currently loaded automatically.
1673                 // Enable loading of images.  Missing images will be loaded without the need for a reload.
1674                 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1675             }
1676
1677             // Consume the event.
1678             return true;
1679         } else if (menuItemId == R.id.dark_webview) {  // Dark WebView.
1680             // Check to see if dark WebView is supported by this WebView.
1681             if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
1682                 // Toggle the dark WebView setting.
1683                 if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) {  // Dark WebView is currently enabled.
1684                     // Turn off dark WebView.
1685                     WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
1686                 } else {  // Dark WebView is currently disabled.
1687                     // Turn on dark WebView.
1688                     WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
1689                 }
1690             }
1691
1692             // Consume the event.
1693             return true;
1694         } else if (menuItemId == R.id.find_on_page) {  // Find on page.
1695             // Get a handle for the views.
1696             Toolbar toolbar = findViewById(R.id.toolbar);
1697             LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1698             EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1699
1700             // Set the minimum height of the find on page linear layout to match the toolbar.
1701             findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1702
1703             // Hide the toolbar.
1704             toolbar.setVisibility(View.GONE);
1705
1706             // Show the find on page linear layout.
1707             findOnPageLinearLayout.setVisibility(View.VISIBLE);
1708
1709             // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
1710             // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1711             findOnPageEditText.postDelayed(() -> {
1712                 // Set the focus on the find on page edit text.
1713                 findOnPageEditText.requestFocus();
1714
1715                 // Get a handle for the input method manager.
1716                 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1717
1718                 // Remove the lint warning below that the input method manager might be null.
1719                 assert inputMethodManager != null;
1720
1721                 // Display the keyboard.  `0` sets no input flags.
1722                 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1723             }, 200);
1724
1725             // Consume the event.
1726             return true;
1727         } else if (menuItemId == R.id.print) {  // Print.
1728             // Get a print manager instance.
1729             PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1730
1731             // Remove the lint error below that print manager might be null.
1732             assert printManager != null;
1733
1734             // Create a print document adapter from the current WebView.
1735             PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1736
1737             // Print the document.
1738             printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
1739
1740             // Consume the event.
1741             return true;
1742         } else if (menuItemId == R.id.save_url) {  // Save URL.
1743             // Check the download preference.
1744             if (downloadWithExternalApp) {  // Download with an external app.
1745                 downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
1746             } else {  // Handle the download inside of Privacy Browser.
1747                 // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
1748                 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
1749                         currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
1750             }
1751
1752             // Consume the event.
1753             return true;
1754         } else if (menuItemId == R.id.save_archive) {
1755             // Instantiate the save dialog.
1756             DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_ARCHIVE, currentWebView.getCurrentUrl(), null, null, null,
1757                     false);
1758
1759             // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
1760             saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1761
1762             return true;
1763         } else if (menuItemId == R.id.save_image) {  // Save image.
1764             // Instantiate the save dialog.
1765             DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, currentWebView.getCurrentUrl(), null, null, null,
1766                     false);
1767
1768             // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
1769             saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1770
1771             // Consume the event.
1772             return true;
1773         } else if (menuItemId == R.id.add_to_homescreen) {  // Add to homescreen.
1774             // Instantiate the create home screen shortcut dialog.
1775             DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1776                     currentWebView.getFavoriteOrDefaultIcon());
1777
1778             // Show the create home screen shortcut dialog.
1779             createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1780
1781             // Consume the event.
1782             return true;
1783         } else if (menuItemId == R.id.view_source) {  // View source.
1784             // Create an intent to launch the view source activity.
1785             Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1786
1787             // Add the variables to the intent.
1788             viewSourceIntent.putExtra(ViewSourceActivityKt.CURRENT_URL, currentWebView.getUrl());
1789             viewSourceIntent.putExtra(ViewSourceActivityKt.USER_AGENT, currentWebView.getSettings().getUserAgentString());
1790
1791             // Make it so.
1792             startActivity(viewSourceIntent);
1793
1794             // Consume the event.
1795             return true;
1796         } else if (menuItemId == R.id.share_url) {  // Share URL.
1797             // Setup the share string.
1798             String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1799
1800             // Create the share intent.
1801             Intent shareIntent = new Intent(Intent.ACTION_SEND);
1802
1803             // Add the share string to the intent.
1804             shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1805
1806             // Set the MIME type.
1807             shareIntent.setType("text/plain");
1808
1809             // Set the intent to open in a new task.
1810             shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1811
1812             // Make it so.
1813             startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1814
1815             // Consume the event.
1816             return true;
1817         } else if (menuItemId == R.id.open_with_app) {  // Open with app.
1818             // Open the URL with an outside app.
1819             openWithApp(currentWebView.getUrl());
1820
1821             // Consume the event.
1822             return true;
1823         } else if (menuItemId == R.id.open_with_browser) {  // Open with browser.
1824             // Open the URL with an outside browser.
1825             openWithBrowser(currentWebView.getUrl());
1826
1827             // Consume the event.
1828             return true;
1829         } else if (menuItemId == R.id.add_or_edit_domain) {  // Add or edit domain.
1830             // Check if domain settings currently exist.
1831             if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
1832                 // Reapply the domain settings on returning to `MainWebViewActivity`.
1833                 reapplyDomainSettingsOnRestart = true;
1834
1835                 // Create an intent to launch the domains activity.
1836                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1837
1838                 // Add the extra information to the intent.
1839                 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1840                 domainsIntent.putExtra("close_on_back", true);
1841                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1842
1843                 // Get the current certificate.
1844                 SslCertificate sslCertificate = currentWebView.getCertificate();
1845
1846                 // Check to see if the SSL certificate is populated.
1847                 if (sslCertificate != null) {
1848                     // Extract the certificate to strings.
1849                     String issuedToCName = sslCertificate.getIssuedTo().getCName();
1850                     String issuedToOName = sslCertificate.getIssuedTo().getOName();
1851                     String issuedToUName = sslCertificate.getIssuedTo().getUName();
1852                     String issuedByCName = sslCertificate.getIssuedBy().getCName();
1853                     String issuedByOName = sslCertificate.getIssuedBy().getOName();
1854                     String issuedByUName = sslCertificate.getIssuedBy().getUName();
1855                     long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1856                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1857
1858                     // Add the certificate to the intent.
1859                     domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1860                     domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1861                     domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1862                     domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1863                     domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1864                     domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1865                     domainsIntent.putExtra("ssl_start_date", startDateLong);
1866                     domainsIntent.putExtra("ssl_end_date", endDateLong);
1867                 }
1868
1869                 // Check to see if the current IP addresses have been received.
1870                 if (currentWebView.hasCurrentIpAddresses()) {
1871                     // Add the current IP addresses to the intent.
1872                     domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1873                 }
1874
1875                 // Make it so.
1876                 startActivity(domainsIntent);
1877             } else {  // Add a new domain.
1878                 // Apply the new domain settings on returning to `MainWebViewActivity`.
1879                 reapplyDomainSettingsOnRestart = true;
1880
1881                 // Get the current domain
1882                 Uri currentUri = Uri.parse(currentWebView.getUrl());
1883                 String currentDomain = currentUri.getHost();
1884
1885                 // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1886                 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1887
1888                 // Create the domain and store the database ID.
1889                 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1890
1891                 // Create an intent to launch the domains activity.
1892                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1893
1894                 // Add the extra information to the intent.
1895                 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1896                 domainsIntent.putExtra("close_on_back", true);
1897                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1898
1899                 // Get the current certificate.
1900                 SslCertificate sslCertificate = currentWebView.getCertificate();
1901
1902                 // Check to see if the SSL certificate is populated.
1903                 if (sslCertificate != null) {
1904                     // Extract the certificate to strings.
1905                     String issuedToCName = sslCertificate.getIssuedTo().getCName();
1906                     String issuedToOName = sslCertificate.getIssuedTo().getOName();
1907                     String issuedToUName = sslCertificate.getIssuedTo().getUName();
1908                     String issuedByCName = sslCertificate.getIssuedBy().getCName();
1909                     String issuedByOName = sslCertificate.getIssuedBy().getOName();
1910                     String issuedByUName = sslCertificate.getIssuedBy().getUName();
1911                     long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1912                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1913
1914                     // Add the certificate to the intent.
1915                     domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1916                     domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1917                     domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1918                     domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1919                     domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1920                     domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1921                     domainsIntent.putExtra("ssl_start_date", startDateLong);
1922                     domainsIntent.putExtra("ssl_end_date", endDateLong);
1923                 }
1924
1925                 // Check to see if the current IP addresses have been received.
1926                 if (currentWebView.hasCurrentIpAddresses()) {
1927                     // Add the current IP addresses to the intent.
1928                     domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1929                 }
1930
1931                 // Make it so.
1932                 startActivity(domainsIntent);
1933             }
1934
1935             // Consume the event.
1936             return true;
1937         } else if (menuItemId == R.id.ad_consent) {  // Ad consent.
1938             // Instantiate the ad consent dialog.
1939             DialogFragment adConsentDialogFragment = new AdConsentDialog();
1940
1941             // Display the ad consent dialog.
1942             adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
1943
1944             // Consume the event.
1945             return true;
1946         } else {  // There is no match with the options menu.  Pass the event up to the parent method.
1947             // Don't consume the event.
1948             return super.onOptionsItemSelected(menuItem);
1949         }
1950     }
1951
1952     // removeAllCookies is deprecated, but it is required for API < 21.
1953     @Override
1954     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1955         // Get a handle for the shared preferences.
1956         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1957
1958         // Get the menu item ID.
1959         int menuItemId = menuItem.getItemId();
1960
1961         // Run the commands that correspond to the selected menu item.
1962         if (menuItemId == R.id.clear_and_exit) {  // Clear and exit.
1963             // Clear and exit Privacy Browser.
1964             clearAndExit();
1965         } else if (menuItemId == R.id.home) {  // Home.
1966             // Load the homepage.
1967             loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
1968         } else if (menuItemId == R.id.back) {  // Back.
1969             // Check if the WebView can go back.
1970             if (currentWebView.canGoBack()) {
1971                 // Get the current web back forward list.
1972                 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1973
1974                 // Get the previous entry URL.
1975                 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
1976
1977                 // Apply the domain settings.
1978                 applyDomainSettings(currentWebView, previousUrl, false, false, false);
1979
1980                 // Load the previous website in the history.
1981                 currentWebView.goBack();
1982             }
1983         } else if (menuItemId == R.id.forward) {  // Forward.
1984             // Check if the WebView can go forward.
1985             if (currentWebView.canGoForward()) {
1986                 // Get the current web back forward list.
1987                 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1988
1989                 // Get the next entry URL.
1990                 String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
1991
1992                 // Apply the domain settings.
1993                 applyDomainSettings(currentWebView, nextUrl, false, false, false);
1994
1995                 // Load the next website in the history.
1996                 currentWebView.goForward();
1997             }
1998         } else if (menuItemId == R.id.history) {  // History.
1999             // Instantiate the URL history dialog.
2000             DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
2001
2002             // Show the URL history dialog.
2003             urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2004         } else if (menuItemId == R.id.open) {  // Open.
2005             // Instantiate the open file dialog.
2006             DialogFragment openDialogFragment = new OpenDialog();
2007
2008             // Show the open file dialog.
2009             openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
2010         } else if (menuItemId == R.id.requests) {  // Requests.
2011             // Populate the resource requests.
2012             RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
2013
2014             // Create an intent to launch the Requests activity.
2015             Intent requestsIntent = new Intent(this, RequestsActivity.class);
2016
2017             // Add the block third-party requests status to the intent.
2018             requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
2019
2020             // Make it so.
2021             startActivity(requestsIntent);
2022         } else if (menuItemId == R.id.downloads) {  // Downloads.
2023             // Try the default system download manager.
2024             try {
2025                 // Launch the default system Download Manager.
2026                 Intent defaultDownloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2027
2028                 // Launch as a new task so that the download manager and Privacy Browser show as separate windows in the recent tasks list.
2029                 defaultDownloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2030
2031                 // Make it so.
2032                 startActivity(defaultDownloadManagerIntent);
2033             } catch (Exception defaultDownloadManagerException) {
2034                 // Try a generic file manager.
2035                 try {
2036                     // Create a generic file manager intent.
2037                     Intent genericFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2038
2039                     // Open the download directory.
2040                     genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR);
2041
2042                     // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2043                     genericFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2044
2045                     // Make it so.
2046                     startActivity(genericFileManagerIntent);
2047                 } catch (Exception genericFileManagerException) {
2048                     // Try an alternate file manager.
2049                     try {
2050                         // Create an alternate file manager intent.
2051                         Intent alternateFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2052
2053                         // Open the download directory.
2054                         alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder");
2055
2056                         // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2057                         alternateFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2058
2059                         // Open the alternate file manager.
2060                         startActivity(alternateFileManagerIntent);
2061                     } catch (Exception alternateFileManagerException) {
2062                         // Display a snackbar.
2063                         Snackbar.make(currentWebView, R.string.no_file_manager_detected, Snackbar.LENGTH_INDEFINITE).show();
2064                     }
2065                 }
2066             }
2067         } else if (menuItemId == R.id.domains) {  // Domains.
2068             // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2069             reapplyDomainSettingsOnRestart = true;
2070
2071             // Launch the domains activity.
2072             Intent domainsIntent = new Intent(this, DomainsActivity.class);
2073
2074             // Add the extra information to the intent.
2075             domainsIntent.putExtra("current_url", currentWebView.getUrl());
2076
2077             // Get the current certificate.
2078             SslCertificate sslCertificate = currentWebView.getCertificate();
2079
2080             // Check to see if the SSL certificate is populated.
2081             if (sslCertificate != null) {
2082                 // Extract the certificate to strings.
2083                 String issuedToCName = sslCertificate.getIssuedTo().getCName();
2084                 String issuedToOName = sslCertificate.getIssuedTo().getOName();
2085                 String issuedToUName = sslCertificate.getIssuedTo().getUName();
2086                 String issuedByCName = sslCertificate.getIssuedBy().getCName();
2087                 String issuedByOName = sslCertificate.getIssuedBy().getOName();
2088                 String issuedByUName = sslCertificate.getIssuedBy().getUName();
2089                 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2090                 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2091
2092                 // Add the certificate to the intent.
2093                 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2094                 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2095                 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2096                 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2097                 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2098                 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2099                 domainsIntent.putExtra("ssl_start_date", startDateLong);
2100                 domainsIntent.putExtra("ssl_end_date", endDateLong);
2101             }
2102
2103             // Check to see if the current IP addresses have been received.
2104             if (currentWebView.hasCurrentIpAddresses()) {
2105                 // Add the current IP addresses to the intent.
2106                 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2107             }
2108
2109             // Make it so.
2110             startActivity(domainsIntent);
2111         } else if (menuItemId == R.id.settings) {  // Settings.
2112             // Set the flag to reapply app settings on restart when returning from Settings.
2113             reapplyAppSettingsOnRestart = true;
2114
2115             // Set the flag to reapply the domain settings on restart when returning from Settings.
2116             reapplyDomainSettingsOnRestart = true;
2117
2118             // Launch the settings activity.
2119             Intent settingsIntent = new Intent(this, SettingsActivity.class);
2120             startActivity(settingsIntent);
2121         } else if (menuItemId == R.id.import_export) { // Import/Export.
2122             // Create an intent to launch the import/export activity.
2123             Intent importExportIntent = new Intent(this, ImportExportActivity.class);
2124
2125             // Make it so.
2126             startActivity(importExportIntent);
2127         } else if (menuItemId == R.id.logcat) {  // Logcat.
2128             // Create an intent to launch the logcat activity.
2129             Intent logcatIntent = new Intent(this, LogcatActivity.class);
2130
2131             // Make it so.
2132             startActivity(logcatIntent);
2133         } else if (menuItemId == R.id.guide) {  // Guide.
2134             // Create an intent to launch the guide activity.
2135             Intent guideIntent = new Intent(this, GuideActivity.class);
2136
2137             // Make it so.
2138             startActivity(guideIntent);
2139         } else if (menuItemId == R.id.about) {  // About
2140             // Create an intent to launch the about activity.
2141             Intent aboutIntent = new Intent(this, AboutActivity.class);
2142
2143             // Create a string array for the blocklist versions.
2144             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],
2145                     ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
2146
2147             // Add the blocklist versions to the intent.
2148             aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions);
2149
2150             // Make it so.
2151             startActivity(aboutIntent);
2152         }
2153
2154         // Close the navigation drawer.
2155         drawerLayout.closeDrawer(GravityCompat.START);
2156         return true;
2157     }
2158
2159     @Override
2160     public void onPostCreate(Bundle savedInstanceState) {
2161         // Run the default commands.
2162         super.onPostCreate(savedInstanceState);
2163
2164         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
2165         actionBarDrawerToggle.syncState();
2166     }
2167
2168     @Override
2169     public void onConfigurationChanged(@NonNull Configuration newConfig) {
2170         // Run the default commands.
2171         super.onConfigurationChanged(newConfig);
2172
2173         // Reload the ad for the free flavor if not in full screen mode.
2174         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2175             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
2176             View adView = findViewById(R.id.adview);
2177
2178             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2179             // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
2180             AdHelper.loadAd(adView, getApplicationContext(), this, getString(R.string.ad_unit_id));
2181         }
2182
2183         // `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:
2184         // https://code.google.com/p/android/issues/detail?id=20493#c8
2185         // ActivityCompat.invalidateOptionsMenu(this);
2186     }
2187
2188     @Override
2189     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2190         // Get the hit test result.
2191         final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2192
2193         // Define the URL strings.
2194         final String imageUrl;
2195         final String linkUrl;
2196
2197         // Get handles for the system managers.
2198         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2199
2200         // Remove the lint errors below that the clipboard manager might be null.
2201         assert clipboardManager != null;
2202
2203         // Process the link according to the type.
2204         switch (hitTestResult.getType()) {
2205             // `SRC_ANCHOR_TYPE` is a link.
2206             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2207                 // Get the target URL.
2208                 linkUrl = hitTestResult.getExtra();
2209
2210                 // Set the target URL as the title of the `ContextMenu`.
2211                 menu.setHeaderTitle(linkUrl);
2212
2213                 // Add an Open in New Tab entry.
2214                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2215                     // Load the link URL in a new tab and move to it.
2216                     addNewTab(linkUrl, true);
2217
2218                     // Consume the event.
2219                     return true;
2220                 });
2221
2222                 // Add an Open in Background entry.
2223                 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2224                     // Load the link URL in a new tab but do not move to it.
2225                     addNewTab(linkUrl, false);
2226
2227                     // Consume the event.
2228                     return true;
2229                 });
2230
2231                 // Add an Open with App entry.
2232                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2233                     openWithApp(linkUrl);
2234
2235                     // Consume the event.
2236                     return true;
2237                 });
2238
2239                 // Add an Open with Browser entry.
2240                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2241                     openWithBrowser(linkUrl);
2242
2243                     // Consume the event.
2244                     return true;
2245                 });
2246
2247                 // Add a Copy URL entry.
2248                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2249                     // Save the link URL in a `ClipData`.
2250                     ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2251
2252                     // Set the `ClipData` as the clipboard's primary clip.
2253                     clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2254
2255                     // Consume the event.
2256                     return true;
2257                 });
2258
2259                 // Add a Save URL entry.
2260                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2261                     // Check the download preference.
2262                     if (downloadWithExternalApp) {  // Download with an external app.
2263                         downloadUrlWithExternalApp(linkUrl);
2264                     } else {  // Handle the download inside of Privacy Browser.
2265                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2266                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2267                                 currentWebView.getAcceptCookies()).execute(linkUrl);
2268                     }
2269
2270                     // Consume the event.
2271                     return true;
2272                 });
2273
2274                 // Add an empty Cancel entry, which by default closes the context menu.
2275                 menu.add(R.string.cancel);
2276                 break;
2277
2278             // `IMAGE_TYPE` is an image.
2279             case WebView.HitTestResult.IMAGE_TYPE:
2280                 // Get the image URL.
2281                 imageUrl = hitTestResult.getExtra();
2282
2283                 // Remove the incorrect lint warning below that the image URL might be null.
2284                 assert imageUrl != null;
2285
2286                 // Set the context menu title.
2287                 if (imageUrl.startsWith("data:")) {  // The image data is contained in within the URL, making it exceedingly long.
2288                     // Truncate the image URL before making it the title.
2289                     menu.setHeaderTitle(imageUrl.substring(0, 100));
2290                 } else {  // The image URL does not contain the full image data.
2291                     // Set the image URL as the title of the context menu.
2292                     menu.setHeaderTitle(imageUrl);
2293                 }
2294
2295                 // Add an Open in New Tab entry.
2296                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2297                     // Load the image in a new tab.
2298                     addNewTab(imageUrl, true);
2299
2300                     // Consume the event.
2301                     return true;
2302                 });
2303
2304                 // Add an Open with App entry.
2305                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2306                     // Open the image URL with an external app.
2307                     openWithApp(imageUrl);
2308
2309                     // Consume the event.
2310                     return true;
2311                 });
2312
2313                 // Add an Open with Browser entry.
2314                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2315                     // Open the image URL with an external browser.
2316                     openWithBrowser(imageUrl);
2317
2318                     // Consume the event.
2319                     return true;
2320                 });
2321
2322                 // Add a View Image entry.
2323                 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2324                     // Load the image in the current tab.
2325                     loadUrl(currentWebView, imageUrl);
2326
2327                     // Consume the event.
2328                     return true;
2329                 });
2330
2331                 // Add a Save Image entry.
2332                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2333                     // Check the download preference.
2334                     if (downloadWithExternalApp) {  // Download with an external app.
2335                         downloadUrlWithExternalApp(imageUrl);
2336                     } else {  // Handle the download inside of Privacy Browser.
2337                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2338                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2339                                 currentWebView.getAcceptCookies()).execute(imageUrl);
2340                     }
2341
2342                     // Consume the event.
2343                     return true;
2344                 });
2345
2346                 // Add a Copy URL entry.
2347                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2348                     // Save the image URL in a clip data.
2349                     ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2350
2351                     // Set the clip data as the clipboard's primary clip.
2352                     clipboardManager.setPrimaryClip(imageTypeClipData);
2353
2354                     // Consume the event.
2355                     return true;
2356                 });
2357
2358                 // Add an empty Cancel entry, which by default closes the context menu.
2359                 menu.add(R.string.cancel);
2360                 break;
2361
2362             // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2363             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2364                 // Get the image URL.
2365                 imageUrl = hitTestResult.getExtra();
2366
2367                 // Instantiate a handler.
2368                 Handler handler = new Handler();
2369
2370                 // Get a message from the handler.
2371                 Message message = handler.obtainMessage();
2372
2373                 // Request the image details from the last touched node be returned in the message.
2374                 currentWebView.requestFocusNodeHref(message);
2375
2376                 // Get the link URL from the message data.
2377                 linkUrl = message.getData().getString("url");
2378
2379                 // Set the link URL as the title of the context menu.
2380                 menu.setHeaderTitle(linkUrl);
2381
2382                 // Add an Open in New Tab entry.
2383                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2384                     // Load the link URL in a new tab and move to it.
2385                     addNewTab(linkUrl, true);
2386
2387                     // Consume the event.
2388                     return true;
2389                 });
2390
2391                 // Add an Open in Background entry.
2392                 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2393                     // Lod the link URL in a new tab but do not move to it.
2394                     addNewTab(linkUrl, false);
2395
2396                     // Consume the event.
2397                     return true;
2398                 });
2399
2400                 // Add an Open Image in New Tab entry.
2401                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2402                     // Load the image in a new tab and move to it.
2403                     addNewTab(imageUrl, true);
2404
2405                     // Consume the event.
2406                     return true;
2407                 });
2408
2409                 // Add an Open with App entry.
2410                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2411                     // Open the link URL with an external app.
2412                     openWithApp(linkUrl);
2413
2414                     // Consume the event.
2415                     return true;
2416                 });
2417
2418                 // Add an Open with Browser entry.
2419                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2420                     // Open the link URL with an external browser.
2421                     openWithBrowser(linkUrl);
2422
2423                     // Consume the event.
2424                     return true;
2425                 });
2426
2427                 // Add a View Image entry.
2428                 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2429                    // View the image in the current tab.
2430                    loadUrl(currentWebView, imageUrl);
2431
2432                    // Consume the event.
2433                    return true;
2434                 });
2435
2436                 // Add a Save Image entry.
2437                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2438                     // Check the download preference.
2439                     if (downloadWithExternalApp) {  // Download with an external app.
2440                         downloadUrlWithExternalApp(imageUrl);
2441                     } else {  // Handle the download inside of Privacy Browser.
2442                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2443                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2444                                 currentWebView.getAcceptCookies()).execute(imageUrl);
2445                     }
2446
2447                     // Consume the event.
2448                     return true;
2449                 });
2450
2451                 // Add a Copy URL entry.
2452                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2453                     // Save the link URL in a clip data.
2454                     ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2455
2456                     // Set the clip data as the clipboard's primary clip.
2457                     clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2458
2459                     // Consume the event.
2460                     return true;
2461                 });
2462
2463                 // Add a Save URL entry.
2464                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2465                     // Check the download preference.
2466                     if (downloadWithExternalApp) {  // Download with an external app.
2467                         downloadUrlWithExternalApp(linkUrl);
2468                     } else {  // Handle the download inside of Privacy Browser.
2469                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2470                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2471                                 currentWebView.getAcceptCookies()).execute(linkUrl);
2472                     }
2473
2474                     // Consume the event.
2475                     return true;
2476                 });
2477
2478                 // Add an empty Cancel entry, which by default closes the context menu.
2479                 menu.add(R.string.cancel);
2480                 break;
2481
2482             case WebView.HitTestResult.EMAIL_TYPE:
2483                 // Get the target URL.
2484                 linkUrl = hitTestResult.getExtra();
2485
2486                 // Set the target URL as the title of the `ContextMenu`.
2487                 menu.setHeaderTitle(linkUrl);
2488
2489                 // Add a Write Email entry.
2490                 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2491                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2492                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2493
2494                     // Parse the url and set it as the data for the `Intent`.
2495                     emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2496
2497                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2498                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2499
2500                     try {
2501                         // Make it so.
2502                         startActivity(emailIntent);
2503                     } catch (ActivityNotFoundException exception) {
2504                         // Display a snackbar.
2505                         Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
2506                     }
2507
2508                     // Consume the event.
2509                     return true;
2510                 });
2511
2512                 // Add a Copy Email Address entry.
2513                 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2514                     // Save the email address in a `ClipData`.
2515                     ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2516
2517                     // Set the `ClipData` as the clipboard's primary clip.
2518                     clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2519
2520                     // Consume the event.
2521                     return true;
2522                 });
2523
2524                 // Add an empty Cancel entry, which by default closes the context menu.
2525                 menu.add(R.string.cancel);
2526                 break;
2527         }
2528     }
2529
2530     @Override
2531     public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2532         // Get a handle for the bookmarks list view.
2533         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2534
2535         // Get the dialog.
2536         Dialog dialog = dialogFragment.getDialog();
2537
2538         // Remove the incorrect lint warning below that the dialog might be null.
2539         assert dialog != null;
2540
2541         // Get the views from the dialog fragment.
2542         EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2543         EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2544
2545         // Extract the strings from the edit texts.
2546         String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2547         String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2548
2549         // Create a favorite icon byte array output stream.
2550         ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2551
2552         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2553         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2554
2555         // Convert the favorite icon byte array stream to a byte array.
2556         byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2557
2558         // Display the new bookmark below the current items in the (0 indexed) list.
2559         int newBookmarkDisplayOrder = bookmarksListView.getCount();
2560
2561         // Create the bookmark.
2562         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2563
2564         // Update the bookmarks cursor with the current contents of this folder.
2565         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2566
2567         // Update the list view.
2568         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2569
2570         // Scroll to the new bookmark.
2571         bookmarksListView.setSelection(newBookmarkDisplayOrder);
2572     }
2573
2574     @Override
2575     public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
2576         // Get a handle for the bookmarks list view.
2577         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2578
2579         // Get the dialog.
2580         Dialog dialog = dialogFragment.getDialog();
2581
2582         // Remove the incorrect lint warning below that the dialog might be null.
2583         assert dialog != null;
2584
2585         // Get handles for the views in the dialog fragment.
2586         EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2587         RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2588         ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2589
2590         // Get new folder name string.
2591         String folderNameString = folderNameEditText.getText().toString();
2592
2593         // Create a folder icon bitmap.
2594         Bitmap folderIconBitmap;
2595
2596         // Set the folder icon bitmap according to the dialog.
2597         if (defaultIconRadioButton.isChecked()) {  // Use the default folder icon.
2598             // Get the default folder icon drawable.
2599             Drawable folderIconDrawable = defaultIconImageView.getDrawable();
2600
2601             // Convert the folder icon drawable to a bitmap drawable.
2602             BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2603
2604             // Convert the folder icon bitmap drawable to a bitmap.
2605             folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2606         } else {  // Use the WebView favorite icon.
2607             // Copy the favorite icon bitmap to the folder icon bitmap.
2608             folderIconBitmap = favoriteIconBitmap;
2609         }
2610
2611         // Create a folder icon byte array output stream.
2612         ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2613
2614         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2615         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2616
2617         // Convert the folder icon byte array stream to a byte array.
2618         byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2619
2620         // Move all the bookmarks down one in the display order.
2621         for (int i = 0; i < bookmarksListView.getCount(); i++) {
2622             int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2623             bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2624         }
2625
2626         // Create the folder, which will be placed at the top of the `ListView`.
2627         bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2628
2629         // Update the bookmarks cursor with the current contents of this folder.
2630         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2631
2632         // Update the `ListView`.
2633         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2634
2635         // Scroll to the new folder.
2636         bookmarksListView.setSelection(0);
2637     }
2638
2639     @Override
2640     public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
2641         // Remove the incorrect lint warning below that the dialog fragment might be null.
2642         assert dialogFragment != null;
2643
2644         // Get the dialog.
2645         Dialog dialog = dialogFragment.getDialog();
2646
2647         // Remove the incorrect lint warning below that the dialog might be null.
2648         assert dialog != null;
2649
2650         // Get handles for the views from the dialog.
2651         RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
2652         RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2653         ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2654         EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2655
2656         // Get the new folder name.
2657         String newFolderNameString = editFolderNameEditText.getText().toString();
2658
2659         // Check if the favorite icon has changed.
2660         if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
2661             // Update the name in the database.
2662             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2663         } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
2664             // Create the new folder icon Bitmap.
2665             Bitmap folderIconBitmap;
2666
2667             // Populate the new folder icon bitmap.
2668             if (defaultFolderIconRadioButton.isChecked()) {
2669                 // Get the default folder icon drawable.
2670                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2671
2672                 // Convert the folder icon drawable to a bitmap drawable.
2673                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2674
2675                 // Convert the folder icon bitmap drawable to a bitmap.
2676                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2677             } else {  // Use the `WebView` favorite icon.
2678                 // Copy the favorite icon bitmap to the folder icon bitmap.
2679                 folderIconBitmap = favoriteIconBitmap;
2680             }
2681
2682             // Create a folder icon byte array output stream.
2683             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2684
2685             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2686             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2687
2688             // Convert the folder icon byte array stream to a byte array.
2689             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2690
2691             // Update the folder icon in the database.
2692             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2693         } else {  // The folder icon and the name have changed.
2694             // Get the new folder icon bitmap.
2695             Bitmap folderIconBitmap;
2696             if (defaultFolderIconRadioButton.isChecked()) {
2697                 // Get the default folder icon drawable.
2698                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2699
2700                 // Convert the folder icon drawable to a bitmap drawable.
2701                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2702
2703                 // Convert the folder icon bitmap drawable to a bitmap.
2704                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2705             } else {  // Use the `WebView` favorite icon.
2706                 // Copy the favorite icon bitmap to the folder icon bitmap.
2707                 folderIconBitmap = favoriteIconBitmap;
2708             }
2709
2710             // Create a folder icon byte array output stream.
2711             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2712
2713             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2714             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2715
2716             // Convert the folder icon byte array stream to a byte array.
2717             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2718
2719             // Update the folder name and icon in the database.
2720             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2721         }
2722
2723         // Update the bookmarks cursor with the current contents of this folder.
2724         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2725
2726         // Update the `ListView`.
2727         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2728     }
2729
2730     // Override `onBackPressed()` to handle the navigation drawer and and the WebViews.
2731     @Override
2732     public void onBackPressed() {
2733         // Check the different options for processing `back`.
2734         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
2735             // Close the navigation drawer.
2736             drawerLayout.closeDrawer(GravityCompat.START);
2737         } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
2738             // close the bookmarks drawer.
2739             drawerLayout.closeDrawer(GravityCompat.END);
2740         } else if (displayingFullScreenVideo) {  // A full screen video is shown.
2741             // Exit the full screen video.
2742             exitFullScreenVideo();
2743         } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
2744             // Get the current web back forward list.
2745             WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2746
2747             // Get the previous entry URL.
2748             String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2749
2750             // Apply the domain settings.
2751             applyDomainSettings(currentWebView, previousUrl, false, false, false);
2752
2753             // Go back.
2754             currentWebView.goBack();
2755         } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
2756             // Close the current tab.
2757             closeCurrentTab();
2758         } else {  // There isn't anything to do in Privacy Browser.
2759             // Close Privacy Browser.  `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
2760             if (Build.VERSION.SDK_INT >= 21) {
2761                 finishAndRemoveTask();
2762             } else {
2763                 finish();
2764             }
2765
2766             // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
2767             System.exit(0);
2768         }
2769     }
2770
2771     // Process the results of a file browse.
2772     @Override
2773     public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
2774         // Run the default commands.
2775         super.onActivityResult(requestCode, resultCode, returnedIntent);
2776
2777         // Run the commands that correlate to the specified request code.
2778         switch (requestCode) {
2779             case BROWSE_FILE_UPLOAD_REQUEST_CODE:
2780                 // File uploads only work on API >= 21.
2781                 if (Build.VERSION.SDK_INT >= 21) {
2782                     // Pass the file to the WebView.
2783                     fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
2784                 }
2785                 break;
2786
2787             case BROWSE_OPEN_REQUEST_CODE:
2788                 // Don't do anything if the user pressed back from the file picker.
2789                 if (resultCode == Activity.RESULT_OK) {
2790                     // Get a handle for the open dialog fragment.
2791                     DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
2792
2793                     // Only update the file name if the dialog still exists.
2794                     if (openDialogFragment != null) {
2795                         // Get a handle for the open dialog.
2796                         Dialog openDialog = openDialogFragment.getDialog();
2797
2798                         // Remove the incorrect lint warning below that the dialog might be null.
2799                         assert openDialog != null;
2800
2801                         // Get a handle for the file name edit text.
2802                         EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
2803
2804                         // Get the file name URI from the intent.
2805                         Uri fileNameUri = returnedIntent.getData();
2806
2807                         // Get the file name string from the URI.
2808                         String fileNameString = fileNameUri.toString();
2809
2810                         // Set the file name text.
2811                         fileNameEditText.setText(fileNameString);
2812
2813                         // Move the cursor to the end of the file name edit text.
2814                         fileNameEditText.setSelection(fileNameString.length());
2815                     }
2816                 }
2817                 break;
2818
2819             case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
2820                 // Don't do anything if the user pressed back from the file picker.
2821                 if (resultCode == Activity.RESULT_OK) {
2822                     // Get a handle for the save dialog fragment.
2823                     DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
2824
2825                     // Only update the file name if the dialog still exists.
2826                     if (saveWebpageDialogFragment != null) {
2827                         // Get a handle for the save webpage dialog.
2828                         Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
2829
2830                         // Remove the incorrect lint warning below that the dialog might be null.
2831                         assert saveWebpageDialog != null;
2832
2833                         // Get a handle for the file name edit text.
2834                         EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
2835
2836                         // Get the file name URI from the intent.
2837                         Uri fileNameUri = returnedIntent.getData();
2838
2839                         // Get the file name string from the URI.
2840                         String fileNameString = fileNameUri.toString();
2841
2842                         // Set the file name text.
2843                         fileNameEditText.setText(fileNameString);
2844
2845                         // Move the cursor to the end of the file name edit text.
2846                         fileNameEditText.setSelection(fileNameString.length());
2847                     }
2848                 }
2849                 break;
2850         }
2851     }
2852
2853     private void loadUrlFromTextBox() {
2854         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
2855         String unformattedUrlString = urlEditText.getText().toString().trim();
2856
2857         // Initialize the formatted URL string.
2858         String url = "";
2859
2860         // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
2861         if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
2862             // Load the entire content URL.
2863             url = unformattedUrlString;
2864         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2865                 unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
2866             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
2867             if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
2868                 unformattedUrlString = "https://" + unformattedUrlString;
2869             }
2870
2871             // Initialize `unformattedUrl`.
2872             URL unformattedUrl = null;
2873
2874             // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
2875             try {
2876                 unformattedUrl = new URL(unformattedUrlString);
2877             } catch (MalformedURLException e) {
2878                 e.printStackTrace();
2879             }
2880
2881             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
2882             String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
2883             String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
2884             String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
2885             String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
2886             String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
2887
2888             // Build the URI.
2889             Uri.Builder uri = new Uri.Builder();
2890             uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
2891
2892             // Decode the URI as a UTF-8 string in.
2893             try {
2894                 url = URLDecoder.decode(uri.build().toString(), "UTF-8");
2895             } catch (UnsupportedEncodingException exception) {
2896                 // Do nothing.  The formatted URL string will remain blank.
2897             }
2898         } else if (!unformattedUrlString.isEmpty()){  // This is not a URL, but rather a search string.
2899             // Create an encoded URL String.
2900             String encodedUrlString;
2901
2902             // Sanitize the search input.
2903             try {
2904                 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
2905             } catch (UnsupportedEncodingException exception) {
2906                 encodedUrlString = "";
2907             }
2908
2909             // Add the base search URL.
2910             url = searchURL + encodedUrlString;
2911         }
2912
2913         // Clear the focus from the URL edit text.  Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
2914         urlEditText.clearFocus();
2915
2916         // Make it so.
2917         loadUrl(currentWebView, url);
2918     }
2919
2920     private void loadUrl(NestedScrollWebView nestedScrollWebView, String url) {
2921         // Sanitize the URL.
2922         url = sanitizeUrl(url);
2923
2924         // Apply the domain settings and load the URL.
2925         applyDomainSettings(nestedScrollWebView, url, true, false, true);
2926     }
2927
2928     public void findPreviousOnPage(View view) {
2929         // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
2930         currentWebView.findNext(false);
2931     }
2932
2933     public void findNextOnPage(View view) {
2934         // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
2935         currentWebView.findNext(true);
2936     }
2937
2938     public void closeFindOnPage(View view) {
2939         // Get a handle for the views.
2940         Toolbar toolbar = findViewById(R.id.toolbar);
2941         LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2942         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2943
2944         // Delete the contents of `find_on_page_edittext`.
2945         findOnPageEditText.setText(null);
2946
2947         // Clear the highlighted phrases if the WebView is not null.
2948         if (currentWebView != null) {
2949             currentWebView.clearMatches();
2950         }
2951
2952         // Hide the find on page linear layout.
2953         findOnPageLinearLayout.setVisibility(View.GONE);
2954
2955         // Show the toolbar.
2956         toolbar.setVisibility(View.VISIBLE);
2957
2958         // Get a handle for the input method manager.
2959         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2960
2961         // Remove the lint warning below that the input method manager might be null.
2962         assert inputMethodManager != null;
2963
2964         // Hide the keyboard.
2965         inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
2966     }
2967
2968     @Override
2969     public void onApplyNewFontSize(DialogFragment dialogFragment) {
2970         // Remove the incorrect lint warning below that the dialog fragment might be null.
2971         assert dialogFragment != null;
2972
2973         // Get the dialog.
2974         Dialog dialog = dialogFragment.getDialog();
2975
2976         // Remove the incorrect lint warning below tha the dialog might be null.
2977         assert dialog != null;
2978
2979         // Get a handle for the font size edit text.
2980         EditText fontSizeEditText = dialog.findViewById(R.id.font_size_edittext);
2981
2982         // Initialize the new font size variable with the current font size.
2983         int newFontSize = currentWebView.getSettings().getTextZoom();
2984
2985         // Get the font size from the edit text.
2986         try {
2987             newFontSize = Integer.parseInt(fontSizeEditText.getText().toString());
2988         } catch (Exception exception) {
2989             // If the edit text does not contain a valid font size do nothing.
2990         }
2991
2992         // Apply the new font size.
2993         currentWebView.getSettings().setTextZoom(newFontSize);
2994     }
2995
2996     @Override
2997     public void onOpen(DialogFragment dialogFragment) {
2998         // Get the dialog.
2999         Dialog dialog = dialogFragment.getDialog();
3000
3001         // Remove the incorrect lint warning below that the dialog might be null.
3002         assert dialog != null;
3003
3004         // Get handles for the views.
3005         EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
3006         CheckBox mhtCheckBox = dialog.findViewById(R.id.mht_checkbox);
3007
3008         // Get the file path string.
3009         String openFilePath = fileNameEditText.getText().toString();
3010
3011         // Apply the domain settings.  This resets the favorite icon and removes any domain settings.
3012         applyDomainSettings(currentWebView, openFilePath, true, false, false);
3013
3014         // Open the file according to the type.
3015         if (mhtCheckBox.isChecked()) {  // Force opening of an MHT file.
3016             try {
3017                 // Get the MHT file input stream.
3018                 InputStream mhtFileInputStream = getContentResolver().openInputStream(Uri.parse(openFilePath));
3019
3020                 // Create a temporary MHT file.
3021                 File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
3022
3023                 // Get a file output stream for the temporary MHT file.
3024                 FileOutputStream temporaryMhtFileOutputStream = new FileOutputStream(temporaryMhtFile);
3025
3026                 // Create a transfer byte array.
3027                 byte[] transferByteArray = new byte[1024];
3028
3029                 // Create an integer to track the number of bytes read.
3030                 int bytesRead;
3031
3032                 // Copy the temporary MHT file input stream to the MHT output stream.
3033                 while ((bytesRead = mhtFileInputStream.read(transferByteArray)) > 0) {
3034                     temporaryMhtFileOutputStream.write(transferByteArray, 0, bytesRead);
3035                 }
3036
3037                 // Flush the temporary MHT file output stream.
3038                 temporaryMhtFileOutputStream.flush();
3039
3040                 // Close the streams.
3041                 temporaryMhtFileOutputStream.close();
3042                 mhtFileInputStream.close();
3043
3044                 // Load the temporary MHT file.
3045                 currentWebView.loadUrl(temporaryMhtFile.toString());
3046             } catch (Exception exception) {
3047                 // Display a snackbar.
3048                 Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
3049             }
3050         } else {  // Let the WebView handle opening of the file.
3051             // Open the file.
3052             currentWebView.loadUrl(openFilePath);
3053         }
3054     }
3055
3056     private void downloadUrlWithExternalApp(String url) {
3057         // Create a download intent.  Not specifying the action type will display the maximum number of options.
3058         Intent downloadIntent = new Intent();
3059
3060         // Set the URI and the mime type.
3061         downloadIntent.setDataAndType(Uri.parse(url), "text/html");
3062
3063         // Flag the intent to open in a new task.
3064         downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3065
3066         // Show the chooser.
3067         startActivity(Intent.createChooser(downloadIntent, getString(R.string.download_with_external_app)));
3068     }
3069
3070     public void onSaveWebpage(int saveType, @NonNull String originalUrlString, DialogFragment dialogFragment) {
3071         // Get the dialog.
3072         Dialog dialog = dialogFragment.getDialog();
3073
3074         // Remove the incorrect lint warning below that the dialog might be null.
3075         assert dialog != null;
3076
3077         // Get a handle for the file name edit text.
3078         EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
3079
3080         // Get the file path from the edit text.
3081         String saveWebpageFilePath = fileNameEditText.getText().toString();
3082
3083         //Save the webpage according to the save type.
3084         switch (saveType) {
3085             case SaveWebpageDialog.SAVE_URL:
3086                 // Get a handle for the dialog URL edit text.
3087                 EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext);
3088
3089                 // Define the save webpage URL.
3090                 String saveWebpageUrl;
3091
3092                 // Store the URL.
3093                 if (originalUrlString.startsWith("data:")) {
3094                     // Save the original URL.
3095                     saveWebpageUrl = originalUrlString;
3096                 } else {
3097                     // Get the URL from the edit text, which may have been modified.
3098                     saveWebpageUrl = dialogUrlEditText.getText().toString();
3099                 }
3100
3101                 // Save the URL.
3102                 new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(saveWebpageUrl);
3103                 break;
3104
3105             case SaveWebpageDialog.SAVE_ARCHIVE:
3106                 try {
3107                     // Create a temporary MHT file.
3108                     File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
3109
3110                     // Save the temporary MHT file.
3111                     currentWebView.saveWebArchive(temporaryMhtFile.toString(), false, callbackValue -> {
3112                         if (callbackValue != null) {  // The temporary MHT file was saved successfully.
3113                             try {
3114                                 // Create a temporary MHT file input stream.
3115                                 FileInputStream temporaryMhtFileInputStream = new FileInputStream(temporaryMhtFile);
3116
3117                                 // Get an output stream for the save webpage file path.
3118                                 OutputStream mhtOutputStream = getContentResolver().openOutputStream(Uri.parse(saveWebpageFilePath));
3119
3120                                 // Create a transfer byte array.
3121                                 byte[] transferByteArray = new byte[1024];
3122
3123                                 // Create an integer to track the number of bytes read.
3124                                 int bytesRead;
3125
3126                                 // Copy the temporary MHT file input stream to the MHT output stream.
3127                                 while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) {
3128                                     mhtOutputStream.write(transferByteArray, 0, bytesRead);
3129                                 }
3130
3131                                 // Close the streams.
3132                                 mhtOutputStream.close();
3133                                 temporaryMhtFileInputStream.close();
3134
3135                                 // Display a snackbar.
3136                                 Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + currentWebView.getCurrentUrl(), Snackbar.LENGTH_SHORT).show();
3137                             } catch (Exception exception) {
3138                                 // Display a snackbar with the exception.
3139                                 Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();
3140                             } finally {
3141                                 // Delete the temporary MHT file.
3142                                 //noinspection ResultOfMethodCallIgnored
3143                                 temporaryMhtFile.delete();
3144                             }
3145                         } else {  // There was an unspecified error while saving the temporary MHT file.
3146                             // Display an error snackbar.
3147                             Snackbar.make(currentWebView, getString(R.string.error_saving_file), Snackbar.LENGTH_INDEFINITE).show();
3148                         }
3149                     });
3150                 } catch (IOException ioException) {
3151                     // Display a snackbar with the IO exception.
3152                     Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + ioException.toString(), Snackbar.LENGTH_INDEFINITE).show();
3153                 }
3154                 break;
3155
3156             case SaveWebpageDialog.SAVE_IMAGE:
3157                 // Save the webpage image.
3158                 new SaveWebpageImage(this, saveWebpageFilePath, currentWebView).execute();
3159                 break;
3160         }
3161     }
3162     
3163     // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
3164     @SuppressLint("ClickableViewAccessibility")
3165     private void initializeApp() {
3166         // Get a handle for the input method.
3167         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
3168
3169         // Remove the lint warning below that the input method manager might be null.
3170         assert inputMethodManager != null;
3171
3172         // Initialize the gray foreground color spans for highlighting the URLs.  The deprecated `getResources()` must be used until API >= 23.
3173         initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
3174         finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
3175
3176         // Get the current theme status.
3177         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
3178
3179         // Set the red color span according to the theme.
3180         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
3181             redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
3182         } else {
3183             redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_900));
3184         }
3185
3186         // Remove the formatting from the URL edit text when the user is editing the text.
3187         urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
3188             if (hasFocus) {  // The user is editing the URL text box.
3189                 // Remove the highlighting.
3190                 urlEditText.getText().removeSpan(redColorSpan);
3191                 urlEditText.getText().removeSpan(initialGrayColorSpan);
3192                 urlEditText.getText().removeSpan(finalGrayColorSpan);
3193             } else {  // The user has stopped editing the URL text box.
3194                 // Move to the beginning of the string.
3195                 urlEditText.setSelection(0);
3196
3197                 // Reapply the highlighting.
3198                 highlightUrlText();
3199             }
3200         });
3201
3202         // Set the go button on the keyboard to load the URL in `urlTextBox`.
3203         urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
3204             // If the event is a key-down event on the `enter` button, load the URL.
3205             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
3206                 // Load the URL into the mainWebView and consume the event.
3207                 loadUrlFromTextBox();
3208
3209                 // If the enter key was pressed, consume the event.
3210                 return true;
3211             } else {
3212                 // If any other key was pressed, do not consume the event.
3213                 return false;
3214             }
3215         });
3216
3217         // Create an Orbot status broadcast receiver.
3218         orbotStatusBroadcastReceiver = new BroadcastReceiver() {
3219             @Override
3220             public void onReceive(Context context, Intent intent) {
3221                 // Store the content of the status message in `orbotStatus`.
3222                 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
3223
3224                 // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
3225                 if ((orbotStatus != null) && orbotStatus.equals("ON") && waitingForProxy) {
3226                     // Reset the waiting for proxy status.
3227                     waitingForProxy = false;
3228
3229                     // Get a list of the current fragments.
3230                     List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
3231
3232                     // Check each fragment to see if it is a waiting for proxy dialog.  Sometimes more than one is displayed.
3233                     for (int i = 0; i < fragmentList.size(); i++) {
3234                         // Get the fragment tag.
3235                         String fragmentTag = fragmentList.get(i).getTag();
3236
3237                         // Check to see if it is the waiting for proxy dialog.
3238                         if ((fragmentTag!= null) && fragmentTag.equals(getString(R.string.waiting_for_proxy_dialog))) {
3239                             // Dismiss the waiting for proxy dialog.
3240                             ((DialogFragment) fragmentList.get(i)).dismiss();
3241                         }
3242                     }
3243
3244                     // Reload existing URLs and load any URLs that are waiting for the proxy.
3245                     for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3246                         // Get the WebView tab fragment.
3247                         WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3248
3249                         // Get the fragment view.
3250                         View fragmentView = webViewTabFragment.getView();
3251
3252                         // Only process the WebViews if they exist.
3253                         if (fragmentView != null) {
3254                             // Get the nested scroll WebView from the tab fragment.
3255                             NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3256
3257                             // Get the waiting for proxy URL string.
3258                             String waitingForProxyUrlString = nestedScrollWebView.getWaitingForProxyUrlString();
3259
3260                             // Load the pending URL if it exists.
3261                             if (!waitingForProxyUrlString.isEmpty()) {  // A URL is waiting to be loaded.
3262                                 // Load the URL.
3263                                 loadUrl(nestedScrollWebView, waitingForProxyUrlString);
3264
3265                                 // Reset the waiting for proxy URL string.
3266                                 nestedScrollWebView.resetWaitingForProxyUrlString();
3267                             } else {  // No URL is waiting to be loaded.
3268                                 // Reload the existing URL.
3269                                 nestedScrollWebView.reload();
3270                             }
3271                         }
3272                     }
3273                 }
3274             }
3275         };
3276
3277         // Register the Orbot status broadcast receiver on `this` context.
3278         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
3279
3280         // Get handles for views that need to be modified.
3281         LinearLayout bookmarksHeaderLinearLayout = findViewById(R.id.bookmarks_header_linearlayout);
3282         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
3283         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
3284         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
3285         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
3286         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
3287
3288         // Update the web view pager every time a tab is modified.
3289         webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
3290             @Override
3291             public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
3292                 // Do nothing.
3293             }
3294
3295             @Override
3296             public void onPageSelected(int position) {
3297                 // Close the find on page bar if it is open.
3298                 closeFindOnPage(null);
3299
3300                 // Set the current WebView.
3301                 setCurrentWebView(position);
3302
3303                 // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled by creating a new tab.
3304                 if (tabLayout.getSelectedTabPosition() != position) {
3305                     // Wait until the new tab has been created.
3306                     tabLayout.post(() -> {
3307                         // Get a handle for the tab.
3308                         TabLayout.Tab tab = tabLayout.getTabAt(position);
3309
3310                         // Assert that the tab is not null.
3311                         assert tab != null;
3312
3313                         // Select the tab.
3314                         tab.select();
3315                     });
3316                 }
3317             }
3318
3319             @Override
3320             public void onPageScrollStateChanged(int state) {
3321                 // Do nothing.
3322             }
3323         });
3324
3325         // Display the View SSL Certificate dialog when the currently selected tab is reselected.
3326         tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
3327             @Override
3328             public void onTabSelected(TabLayout.Tab tab) {
3329                 // Select the same page in the view pager.
3330                 webViewPager.setCurrentItem(tab.getPosition());
3331             }
3332
3333             @Override
3334             public void onTabUnselected(TabLayout.Tab tab) {
3335                 // Do nothing.
3336             }
3337
3338             @Override
3339             public void onTabReselected(TabLayout.Tab tab) {
3340                 // Instantiate the View SSL Certificate dialog.
3341                 DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
3342
3343                 // Display the View SSL Certificate dialog.
3344                 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
3345             }
3346         });
3347
3348         // Set a touch listener on the bookmarks header linear layout so that touches don't pass through to the button underneath.
3349         bookmarksHeaderLinearLayout.setOnTouchListener((view, motionEvent) -> {
3350             // Consume the touch.
3351             return true;
3352         });
3353
3354         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
3355         launchBookmarksActivityFab.setOnClickListener(v -> {
3356             // Get a copy of the favorite icon bitmap.
3357             Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
3358
3359             // Create a favorite icon byte array output stream.
3360             ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3361
3362             // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
3363             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3364
3365             // Convert the favorite icon byte array stream to a byte array.
3366             byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3367
3368             // Create an intent to launch the bookmarks activity.
3369             Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
3370
3371             // Add the extra information to the intent.
3372             bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
3373             bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
3374             bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
3375             bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
3376
3377             // Make it so.
3378             startActivity(bookmarksIntent);
3379         });
3380
3381         // Set the create new bookmark folder FAB to display an alert dialog.
3382         createBookmarkFolderFab.setOnClickListener(v -> {
3383             // Create a create bookmark folder dialog.
3384             DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
3385
3386             // Show the create bookmark folder dialog.
3387             createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
3388         });
3389
3390         // Set the create new bookmark FAB to display an alert dialog.
3391         createBookmarkFab.setOnClickListener(view -> {
3392             // Instantiate the create bookmark dialog.
3393             DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
3394
3395             // Display the create bookmark dialog.
3396             createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
3397         });
3398
3399         // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
3400         findOnPageEditText.addTextChangedListener(new TextWatcher() {
3401             @Override
3402             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
3403                 // Do nothing.
3404             }
3405
3406             @Override
3407             public void onTextChanged(CharSequence s, int start, int before, int count) {
3408                 // Do nothing.
3409             }
3410
3411             @Override
3412             public void afterTextChanged(Editable s) {
3413                 // Search for the text in the WebView if it is not null.  Sometimes on resume after a period of non-use the WebView will be null.
3414                 if (currentWebView != null) {
3415                     currentWebView.findAllAsync(findOnPageEditText.getText().toString());
3416                 }
3417             }
3418         });
3419
3420         // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
3421         findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
3422             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
3423                 // Hide the soft keyboard.
3424                 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3425
3426                 // Consume the event.
3427                 return true;
3428             } else {  // A different key was pressed.
3429                 // Do not consume the event.
3430                 return false;
3431             }
3432         });
3433
3434         // Implement swipe to refresh.
3435         swipeRefreshLayout.setOnRefreshListener(() -> {
3436             // Check the visibility of the bottom app bar.  Sometimes it is hidden if the WebView is the same size as the visible screen.
3437             if (bottomAppBar && scrollAppBar && (appBarLayout.getVisibility() == View.GONE)) {  // The bottom app bar is currently hidden.
3438                 // Show the app bar.
3439                 appBarLayout.setVisibility(View.VISIBLE);
3440
3441                 // Disable the refreshing animation.
3442                 swipeRefreshLayout.setRefreshing(false);
3443             } else {  // A bottom app bar is not currently hidden.
3444                 // Reload the website.
3445                 currentWebView.reload();
3446             }
3447         });
3448
3449         // Store the default progress view offsets for use later in `initializeWebView()`.
3450         defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
3451         defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
3452
3453         // Set the refresh color scheme according to the theme.
3454         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
3455             swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
3456         } else {
3457             swipeRefreshLayout.setColorSchemeResources(R.color.violet_500);
3458         }
3459
3460         // Initialize a color background typed value.
3461         TypedValue colorBackgroundTypedValue = new TypedValue();
3462
3463         // Get the color background from the theme.
3464         getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
3465
3466         // Get the color background int from the typed value.
3467         int colorBackgroundInt = colorBackgroundTypedValue.data;
3468
3469         // Set the swipe refresh background color.
3470         swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
3471
3472         // The drawer titles identify the drawer layouts in accessibility mode.
3473         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
3474         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
3475
3476         // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
3477         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
3478
3479         // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
3480         currentBookmarksFolder = "";
3481
3482         // Load the home folder, which is `""` in the database.
3483         loadBookmarksFolder();
3484
3485         bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
3486             // Convert the id from long to int to match the format of the bookmarks database.
3487             int databaseId = (int) id;
3488
3489             // Get the bookmark cursor for this ID.
3490             Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
3491
3492             // Move the bookmark cursor to the first row.
3493             bookmarkCursor.moveToFirst();
3494
3495             // Act upon the bookmark according to the type.
3496             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
3497                 // Store the new folder name in `currentBookmarksFolder`.
3498                 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3499
3500                 // Load the new folder.
3501                 loadBookmarksFolder();
3502             } else {  // The selected bookmark is not a folder.
3503                 // Load the bookmark URL.
3504                 loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
3505
3506                 // Close the bookmarks drawer.
3507                 drawerLayout.closeDrawer(GravityCompat.END);
3508             }
3509
3510             // Close the `Cursor`.
3511             bookmarkCursor.close();
3512         });
3513
3514         bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
3515             // Convert the database ID from `long` to `int`.
3516             int databaseId = (int) id;
3517
3518             // Find out if the selected bookmark is a folder.
3519             boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
3520
3521             // Check to see if the bookmark is a folder.
3522             if (isFolder) {  // The bookmark is a folder.
3523                 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
3524                 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3525
3526                 // Instantiate the edit folder bookmark dialog.
3527                 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
3528
3529                 // Show the edit folder bookmark dialog.
3530                 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
3531             } else {  // The bookmark is not a folder.
3532                 // Get the bookmark cursor for this ID.
3533                 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
3534
3535                 // Move the bookmark cursor to the first row.
3536                 bookmarkCursor.moveToFirst();
3537
3538                 // Load the bookmark in a new tab but do not switch to the tab or close the drawer.
3539                 addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
3540
3541                 // Display a snackbar.
3542                 Snackbar.make(currentWebView, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
3543             }
3544
3545             // Consume the event.
3546             return true;
3547         });
3548
3549         // The drawer listener is used to update the navigation menu.
3550         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
3551             @Override
3552             public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
3553             }
3554
3555             @Override
3556             public void onDrawerOpened(@NonNull View drawerView) {
3557             }
3558
3559             @Override
3560             public void onDrawerClosed(@NonNull View drawerView) {
3561                 // Reset the drawer icon when the drawer is closed.  Otherwise, it is an arrow if the drawer is open when the app is restarted.
3562                 actionBarDrawerToggle.syncState();
3563             }
3564
3565             @Override
3566             public void onDrawerStateChanged(int newState) {
3567                 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) {  // A drawer is opening or closing.
3568                     // Update the navigation menu items if the WebView is not null.
3569                     if (currentWebView != null) {
3570                         navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
3571                         navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
3572                         navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
3573                         navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
3574
3575                         // Hide the keyboard (if displayed).
3576                         inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3577                     }
3578
3579                     // Clear the focus from from the URL text box and the WebView.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
3580                     urlEditText.clearFocus();
3581                     currentWebView.clearFocus();
3582                 }
3583             }
3584         });
3585
3586         // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
3587         customHeaders.put("X-Requested-With", "");
3588
3589         // Inflate a bare WebView to get the default user agent.  It is not used to render content on the screen.
3590         @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
3591
3592         // Get a handle for the WebView.
3593         WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
3594
3595         // Store the default user agent.
3596         webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
3597
3598         // Destroy the bare WebView.
3599         bareWebView.destroy();
3600     }
3601
3602     private void applyAppSettings() {
3603         // Get a handle for the shared preferences.
3604         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3605
3606         // Store the values from the shared preferences in variables.
3607         incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
3608         sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
3609         sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
3610         sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
3611         proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
3612         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
3613         downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false);
3614         hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
3615         scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
3616
3617         // Apply the saved proxy mode if the app has been restarted.
3618         if (savedProxyMode != null) {
3619             // Apply the saved proxy mode.
3620             proxyMode = savedProxyMode;
3621
3622             // Reset the saved proxy mode.
3623             savedProxyMode = null;
3624         }
3625
3626         // Get the search string.
3627         String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
3628
3629         // Set the search string.
3630         if (searchString.equals("Custom URL")) {  // A custom search string is used.
3631             searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
3632         } else {  // A custom search string is not used.
3633             searchURL = searchString;
3634         }
3635
3636         // Apply the proxy.
3637         applyProxy(false);
3638
3639         // Adjust the layout and scrolling parameters if the app bar is at the top of the screen.
3640         if (!bottomAppBar) {
3641             // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
3642             CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
3643             AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
3644             AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
3645             AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
3646
3647             // Add the scrolling behavior to the layout parameters.
3648             if (scrollAppBar) {
3649                 // Enable scrolling of the app bar.
3650                 swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
3651                 toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3652                 findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3653                 tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3654             } else {
3655                 // Disable scrolling of the app bar.
3656                 swipeRefreshLayoutParams.setBehavior(null);
3657                 toolbarLayoutParams.setScrollFlags(0);
3658                 findOnPageLayoutParams.setScrollFlags(0);
3659                 tabsLayoutParams.setScrollFlags(0);
3660
3661                 // Expand the app bar if it is currently collapsed.
3662                 appBarLayout.setExpanded(true);
3663             }
3664
3665             // Set the app bar scrolling for each WebView.
3666             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3667                 // Get the WebView tab fragment.
3668                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3669
3670                 // Get the fragment view.
3671                 View fragmentView = webViewTabFragment.getView();
3672
3673                 // Only modify the WebViews if they exist.
3674                 if (fragmentView != null) {
3675                     // Get the nested scroll WebView from the tab fragment.
3676                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3677
3678                     // Set the app bar scrolling.
3679                     nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
3680                 }
3681             }
3682         }
3683
3684         // Update the full screen browsing mode settings.
3685         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
3686             // Update the visibility of the app bar, which might have changed in the settings.
3687             if (hideAppBar) {
3688                 // Hide the tab linear layout.
3689                 tabsLinearLayout.setVisibility(View.GONE);
3690
3691                 // Hide the action bar.
3692                 actionBar.hide();
3693             } else {
3694                 // Show the tab linear layout.
3695                 tabsLinearLayout.setVisibility(View.VISIBLE);
3696
3697                 // Show the action bar.
3698                 actionBar.show();
3699             }
3700
3701             // Hide the banner ad in the free flavor.
3702             if (BuildConfig.FLAVOR.contentEquals("free")) {
3703                 // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
3704                 View adView = findViewById(R.id.adview);
3705
3706                 // Hide the banner ad.
3707                 AdHelper.hideAd(adView);
3708             }
3709
3710             /* Hide the system bars.
3711              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
3712              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
3713              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
3714              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
3715              */
3716             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
3717                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
3718         } else {  // Privacy Browser is not in full screen browsing mode.
3719             // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
3720             inFullScreenBrowsingMode = false;
3721
3722             // Show the tab linear layout.
3723             tabsLinearLayout.setVisibility(View.VISIBLE);
3724
3725             // Show the action bar.
3726             actionBar.show();
3727
3728             // Show the banner ad in the free flavor.
3729             if (BuildConfig.FLAVOR.contentEquals("free")) {
3730                 // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
3731                 View adView = findViewById(R.id.adview);
3732
3733                 // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
3734                 // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
3735                 AdHelper.initializeAds(adView, getApplicationContext(), this, getSupportFragmentManager(), getString(R.string.ad_unit_id));
3736             }
3737
3738             // Remove the `SYSTEM_UI` flags from the root frame layout.
3739             rootFrameLayout.setSystemUiVisibility(0);
3740         }
3741     }
3742
3743     @Override
3744     public void navigateHistory(@NonNull String url, int steps) {
3745         // Apply the domain settings.
3746         applyDomainSettings(currentWebView, url, false, false, false);
3747
3748         // Load the history entry.
3749         currentWebView.goBackOrForward(steps);
3750     }
3751
3752     @Override
3753     public void pinnedErrorGoBack() {
3754         // Get the current web back forward list.
3755         WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
3756
3757         // Get the previous entry URL.
3758         String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
3759
3760         // Apply the domain settings.
3761         applyDomainSettings(currentWebView, previousUrl, false, false, false);
3762
3763         // Go back.
3764         currentWebView.goBack();
3765     }
3766
3767     // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
3768     @SuppressLint("SetJavaScriptEnabled")
3769     private void applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite, boolean loadUrl) {
3770         // Store the current URL.
3771         nestedScrollWebView.setCurrentUrl(url);
3772
3773         // Parse the URL into a URI.
3774         Uri uri = Uri.parse(url);
3775
3776         // Extract the domain from `uri`.
3777         String newHostName = uri.getHost();
3778
3779         // Strings don't like to be null.
3780         if (newHostName == null) {
3781             newHostName = "";
3782         }
3783
3784         // Apply the domain settings if a new domain is being loaded or if the new domain is blank.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
3785         if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName) || newHostName.equals("")) {
3786             // Set the new host name as the current domain name.
3787             nestedScrollWebView.setCurrentDomainName(newHostName);
3788
3789             // Reset the ignoring of pinned domain information.
3790             nestedScrollWebView.setIgnorePinnedDomainInformation(false);
3791
3792             // Clear any pinned SSL certificate or IP addresses.
3793             nestedScrollWebView.clearPinnedSslCertificate();
3794             nestedScrollWebView.clearPinnedIpAddresses();
3795
3796             // Reset the favorite icon if specified.
3797             if (resetTab) {
3798                 // Initialize the favorite icon.
3799                 nestedScrollWebView.initializeFavoriteIcon();
3800
3801                 // Get the current page position.
3802                 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
3803
3804                 // Get the corresponding tab.
3805                 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
3806
3807                 // Update the tab if it isn't null, which sometimes happens when restarting from the background.
3808                 if (tab != null) {
3809                     // Get the tab custom view.
3810                     View tabCustomView = tab.getCustomView();
3811
3812                     // Remove the warning below that the tab custom view might be null.
3813                     assert tabCustomView != null;
3814
3815                     // Get the tab views.
3816                     ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
3817                     TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
3818
3819                     // Set the default favorite icon as the favorite icon for this tab.
3820                     tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
3821
3822                     // Set the loading title text.
3823                     tabTitleTextView.setText(R.string.loading);
3824                 }
3825             }
3826
3827             // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
3828             DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
3829
3830             // Get a full cursor from `domainsDatabaseHelper`.
3831             Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
3832
3833             // Initialize `domainSettingsSet`.
3834             Set<String> domainSettingsSet = new HashSet<>();
3835
3836             // Get the domain name column index.
3837             int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
3838
3839             // Populate `domainSettingsSet`.
3840             for (int i = 0; i < domainNameCursor.getCount(); i++) {
3841                 // Move `domainsCursor` to the current row.
3842                 domainNameCursor.moveToPosition(i);
3843
3844                 // Store the domain name in `domainSettingsSet`.
3845                 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
3846             }
3847
3848             // Close `domainNameCursor.
3849             domainNameCursor.close();
3850
3851             // Initialize the domain name in database variable.
3852             String domainNameInDatabase = null;
3853
3854             // Check the hostname against the domain settings set.
3855             if (domainSettingsSet.contains(newHostName)) {  // The hostname is contained in the domain settings set.
3856                 // Record the domain name in the database.
3857                 domainNameInDatabase = newHostName;
3858
3859                 // Set the domain settings applied tracker to true.
3860                 nestedScrollWebView.setDomainSettingsApplied(true);
3861             } else {  // The hostname is not contained in the domain settings set.
3862                 // Set the domain settings applied tracker to false.
3863                 nestedScrollWebView.setDomainSettingsApplied(false);
3864             }
3865
3866             // Check all the subdomains of the host name against wildcard domains in the domain cursor.
3867             while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the host name.
3868                 if (domainSettingsSet.contains("*." + newHostName)) {  // Check the host name prepended by `*.`.
3869                     // Set the domain settings applied tracker to true.
3870                     nestedScrollWebView.setDomainSettingsApplied(true);
3871
3872                     // Store the applied domain names as it appears in the database.
3873                     domainNameInDatabase = "*." + newHostName;
3874                 }
3875
3876                 // Strip out the lowest subdomain of of the host name.
3877                 newHostName = newHostName.substring(newHostName.indexOf(".") + 1);
3878             }
3879
3880
3881             // Get a handle for the shared preferences.
3882             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3883
3884             // Store the general preference information.
3885             String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
3886             String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
3887             boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
3888             String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
3889             boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
3890             boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
3891
3892             // Get the WebView theme entry values string array.
3893             String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values);
3894
3895             // Get a handle for the cookie manager.
3896             CookieManager cookieManager = CookieManager.getInstance();
3897
3898             // Initialize the user agent array adapter and string array.
3899             ArrayAdapter<CharSequence> userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
3900             String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
3901
3902             if (nestedScrollWebView.getDomainSettingsApplied()) {  // The url has custom domain settings.
3903                 // Get a cursor for the current host and move it to the first position.
3904                 Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
3905                 currentDomainSettingsCursor.moveToFirst();
3906
3907                 // Get the settings from the cursor.
3908                 nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
3909                 nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
3910                 nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.COOKIES)) == 1);
3911                 nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
3912                 // Form data can be removed once the minimum API >= 26.
3913                 boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
3914                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST,
3915                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
3916                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY,
3917                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
3918                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST,
3919                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
3920                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST,
3921                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
3922                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1);
3923                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY,
3924                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
3925                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS,
3926                         currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
3927                 String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
3928                 int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
3929                 int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
3930                 int webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WEBVIEW_THEME));
3931                 int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT));
3932                 int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
3933                 boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
3934                 String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
3935                 String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
3936                 String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
3937                 String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
3938                 String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
3939                 String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
3940                 boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
3941                 String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
3942
3943                 // Get the pinned SSL date longs.
3944                 long pinnedSslStartDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE));
3945                 long pinnedSslEndDateLong = currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE));
3946
3947                 // Define the pinned SSL date variables.
3948                 Date pinnedSslStartDate;
3949                 Date pinnedSslEndDate;
3950
3951                 // Set the pinned SSL certificate start date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0.
3952                 if (pinnedSslStartDateLong == 0) {
3953                     pinnedSslStartDate = null;
3954                 } else {
3955                     pinnedSslStartDate = new Date(pinnedSslStartDateLong);
3956                 }
3957
3958                 // Set the pinned SSL certificate end date to `null` if the saved date long is 0 because creating a new date results in an error if the input is 0.
3959                 if (pinnedSslEndDateLong == 0) {
3960                     pinnedSslEndDate = null;
3961                 } else {
3962                     pinnedSslEndDate = new Date(pinnedSslEndDateLong);
3963                 }
3964
3965                 // Close the current host domain settings cursor.
3966                 currentDomainSettingsCursor.close();
3967
3968                 // If there is a pinned SSL certificate, store it in the WebView.
3969                 if (pinnedSslCertificate) {
3970                     nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName,
3971                             pinnedSslStartDate, pinnedSslEndDate);
3972                 }
3973
3974                 // If there is a pinned IP address, store it in the WebView.
3975                 if (pinnedIpAddresses) {
3976                     nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses);
3977                 }
3978
3979                 // Apply the cookie domain settings.
3980                 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
3981
3982                 // Apply the form data setting if the API < 26.
3983                 if (Build.VERSION.SDK_INT < 26) {
3984                     nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
3985                 }
3986
3987                 // Apply the font size.
3988                 try {  // Try the specified font size to see if it is valid.
3989                     if (fontSize == 0) {  // Apply the default font size.
3990                             // Try to set the font size from the value in the app settings.
3991                             nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
3992                     } else {  // Apply the font size from domain settings.
3993                         nestedScrollWebView.getSettings().setTextZoom(fontSize);
3994                     }
3995                 } catch (Exception exception) {  // The specified font size is invalid
3996                     // Set the font size to be 100%
3997                     nestedScrollWebView.getSettings().setTextZoom(100);
3998                 }
3999
4000                 // Set the user agent.
4001                 if (userAgentName.equals(getString(R.string.system_default_user_agent))) {  // Use the system default user agent.
4002                     // Get the array position of the default user agent name.
4003                     int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4004
4005                     // Set the user agent according to the system default.
4006                     switch (defaultUserAgentArrayPosition) {
4007                         case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
4008                             // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4009                             nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
4010                             break;
4011
4012                         case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4013                             // Set the user agent to `""`, which uses the default value.
4014                             nestedScrollWebView.getSettings().setUserAgentString("");
4015                             break;
4016
4017                         case SETTINGS_CUSTOM_USER_AGENT:
4018                             // Set the default custom user agent.
4019                             nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
4020                             break;
4021
4022                         default:
4023                             // Get the user agent string from the user agent data array
4024                             nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4025                     }
4026                 } else {  // Set the user agent according to the stored name.
4027                     // Get the array position of the user agent name.
4028                     int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4029
4030                     switch (userAgentArrayPosition) {
4031                         case UNRECOGNIZED_USER_AGENT:  // The user agent name contains a custom user agent.
4032                             nestedScrollWebView.getSettings().setUserAgentString(userAgentName);
4033                             break;
4034
4035                         case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4036                             // Set the user agent to `""`, which uses the default value.
4037                             nestedScrollWebView.getSettings().setUserAgentString("");
4038                             break;
4039
4040                         default:
4041                             // Get the user agent string from the user agent data array.
4042                             nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4043                     }
4044                 }
4045
4046                 // Set swipe to refresh.
4047                 switch (swipeToRefreshInt) {
4048                     case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4049                         // Store the swipe to refresh status in the nested scroll WebView.
4050                         nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
4051
4052                         // Update the swipe refresh layout.
4053                         if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
4054                             // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
4055                             swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
4056                         } else {  // Swipe to refresh is disabled.
4057                             // Disable the swipe refresh layout.
4058                             swipeRefreshLayout.setEnabled(false);
4059                         }
4060                         break;
4061
4062                     case DomainsDatabaseHelper.ENABLED:
4063                         // Store the swipe to refresh status in the nested scroll WebView.
4064                         nestedScrollWebView.setSwipeToRefresh(true);
4065
4066                         // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
4067                         swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
4068                         break;
4069
4070                     case DomainsDatabaseHelper.DISABLED:
4071                         // Store the swipe to refresh status in the nested scroll WebView.
4072                         nestedScrollWebView.setSwipeToRefresh(false);
4073
4074                         // Disable swipe to refresh.
4075                         swipeRefreshLayout.setEnabled(false);
4076                 }
4077
4078                 // Check to see if WebView themes are supported.
4079                 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
4080                     // Set the WebView theme.
4081                     switch (webViewThemeInt) {
4082                         case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4083                             // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
4084                             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
4085                                 // Turn off the WebView dark mode.
4086                                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4087                             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
4088                                 // Turn on the WebView dark mode.
4089                                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4090                             } else {  // The system default theme is selected.
4091                                 // Get the current system theme status.
4092                                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4093
4094                                 // Set the WebView theme according to the current system theme status.
4095                                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
4096                                     // Turn off the WebView dark mode.
4097                                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4098                                 } else {  // The system is in night mode.
4099                                     // Turn on the WebView dark mode.
4100                                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4101                                 }
4102                             }
4103                             break;
4104
4105                         case DomainsDatabaseHelper.LIGHT_THEME:
4106                             // Turn off the WebView dark mode.
4107                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4108                             break;
4109
4110                         case DomainsDatabaseHelper.DARK_THEME:
4111                             // Turn on the WebView dark mode.
4112                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4113                             break;
4114                     }
4115                 }
4116
4117                 // Set the viewport.
4118                 switch (wideViewportInt) {
4119                     case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4120                         nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
4121                         break;
4122
4123                     case DomainsDatabaseHelper.ENABLED:
4124                         nestedScrollWebView.getSettings().setUseWideViewPort(true);
4125                         break;
4126
4127                     case DomainsDatabaseHelper.DISABLED:
4128                         nestedScrollWebView.getSettings().setUseWideViewPort(false);
4129                         break;
4130                 }
4131
4132                 // Set the loading of webpage images.
4133                 switch (displayWebpageImagesInt) {
4134                     case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4135                         nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4136                         break;
4137
4138                     case DomainsDatabaseHelper.ENABLED:
4139                         nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true);
4140                         break;
4141
4142                     case DomainsDatabaseHelper.DISABLED:
4143                         nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false);
4144                         break;
4145                 }
4146
4147                 // Get the current theme status.
4148                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4149
4150                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
4151                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4152                     urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
4153                 } else {
4154                     urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
4155                 }
4156             } else {  // The new URL does not have custom domain settings.  Load the defaults.
4157                 // Store the values from the shared preferences.
4158                 nestedScrollWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
4159                 nestedScrollWebView.setAcceptCookies(sharedPreferences.getBoolean(getString(R.string.cookies_key), false));
4160                 nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
4161                 boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
4162                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true));
4163                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
4164                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true));
4165                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
4166                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true));
4167                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
4168                 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
4169
4170                 // Apply the default cookie setting.
4171                 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
4172
4173                 // Apply the default font size setting.
4174                 try {
4175                     // Try to set the font size from the value in the app settings.
4176                     nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
4177                 } catch (Exception exception) {
4178                     // If the app settings value is invalid, set the font size to 100%.
4179                     nestedScrollWebView.getSettings().setTextZoom(100);
4180                 }
4181
4182                 // Apply the form data setting if the API < 26.
4183                 if (Build.VERSION.SDK_INT < 26) {
4184                     nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
4185                 }
4186
4187                 // Store the swipe to refresh status in the nested scroll WebView.
4188                 nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
4189
4190                 // Update the swipe refresh layout.
4191                 if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
4192                     // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
4193                     swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
4194                 } else {  // Swipe to refresh is disabled.
4195                     // Disable the swipe refresh layout.
4196                     swipeRefreshLayout.setEnabled(false);
4197                 }
4198
4199                 // Reset the pinned variables.
4200                 nestedScrollWebView.setDomainSettingsDatabaseId(-1);
4201
4202                 // Get the array position of the user agent name.
4203                 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4204
4205                 // Set the user agent.
4206                 switch (userAgentArrayPosition) {
4207                     case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
4208                         // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4209                         nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
4210                         break;
4211
4212                     case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4213                         // Set the user agent to `""`, which uses the default value.
4214                         nestedScrollWebView.getSettings().setUserAgentString("");
4215                         break;
4216
4217                     case SETTINGS_CUSTOM_USER_AGENT:
4218                         // Set the default custom user agent.
4219                         nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
4220                         break;
4221
4222                     default:
4223                         // Get the user agent string from the user agent data array
4224                         nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4225                 }
4226
4227                 // Apply the WebView theme if supported by the installed WebView.
4228                 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
4229                     // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
4230                     if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
4231                         // Turn off the WebView dark mode.
4232                         WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4233                     } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
4234                         // Turn on the WebView dark mode.
4235                         WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4236                     } else {  // The system default theme is selected.
4237                         // Get the current system theme status.
4238                         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4239
4240                         // Set the WebView theme according to the current system theme status.
4241                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
4242                             // Turn off the WebView dark mode.
4243                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4244                         } else {  // The system is in night mode.
4245                             // Turn on the WebView dark mode.
4246                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4247                         }
4248                     }
4249                 }
4250
4251                 // Set the viewport.
4252                 nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
4253
4254                 // Set the loading of webpage images.
4255                 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4256
4257                 // Set a transparent background on the URL relative layout.
4258                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
4259             }
4260
4261             // Close the domains database helper.
4262             domainsDatabaseHelper.close();
4263
4264             // Update the privacy icons.
4265             updatePrivacyIcons(true);
4266         }
4267
4268         // Reload the website if returning from the Domains activity.
4269         if (reloadWebsite) {
4270             nestedScrollWebView.reload();
4271         }
4272
4273         // Load the URL if directed.  This makes sure that the domain settings are properly loaded before the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
4274         if (loadUrl) {
4275             nestedScrollWebView.loadUrl(url, customHeaders);
4276         }
4277     }
4278
4279     private void applyProxy(boolean reloadWebViews) {
4280         // Set the proxy according to the mode.  `this` refers to the current activity where an alert dialog might be displayed.
4281         ProxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode);
4282
4283         // Reset the waiting for proxy tracker.
4284         waitingForProxy = false;
4285
4286         // Get the current theme status.
4287         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4288
4289         // Update the user interface and reload the WebViews if requested.
4290         switch (proxyMode) {
4291             case ProxyHelper.NONE:
4292                 // Initialize a color background typed value.
4293                 TypedValue colorBackgroundTypedValue = new TypedValue();
4294
4295                 // Get the color background from the theme.
4296                 getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
4297
4298                 // Get the color background int from the typed value.
4299                 int colorBackgroundInt = colorBackgroundTypedValue.data;
4300
4301                 // Set the default app bar layout background.
4302                 appBarLayout.setBackgroundColor(colorBackgroundInt);
4303                 break;
4304
4305             case ProxyHelper.TOR:
4306                 // Set the app bar background to indicate proxying through Orbot is enabled.
4307                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4308                     appBarLayout.setBackgroundResource(R.color.blue_50);
4309                 } else {
4310                     appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4311                 }
4312
4313                 // Check to see if Orbot is installed.
4314                 try {
4315                     // Get the package manager.
4316                     PackageManager packageManager = getPackageManager();
4317
4318                     // Check to see if Orbot is in the list.  This will throw an error and drop to the catch section if it isn't installed.
4319                     packageManager.getPackageInfo("org.torproject.android", 0);
4320
4321                     // Check to see if the proxy is ready.
4322                     if (!orbotStatus.equals("ON")) {  // Orbot is not ready.
4323                         // Set the waiting for proxy status.
4324                         waitingForProxy = true;
4325
4326                         // Show the waiting for proxy dialog if it isn't already displayed.
4327                         if (getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) {
4328                             // Get a handle for the waiting for proxy alert dialog.
4329                             DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog();
4330
4331                             // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
4332                             try {
4333                                 // Show the waiting for proxy alert dialog.
4334                                 waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
4335                             } catch (Exception waitingForTorException) {
4336                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
4337                                 pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
4338                             }
4339                         }
4340                     }
4341                 } catch (PackageManager.NameNotFoundException exception) {  // Orbot is not installed.
4342                     // Show the Orbot not installed dialog if it is not already displayed.
4343                     if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
4344                         // Get a handle for the Orbot not installed alert dialog.
4345                         DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
4346
4347                         // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
4348                         try {
4349                             // Display the Orbot not installed alert dialog.
4350                             orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
4351                         } catch (Exception orbotNotInstalledException) {
4352                             // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
4353                             pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
4354                         }
4355                     }
4356                 }
4357                 break;
4358
4359             case ProxyHelper.I2P:
4360                 // Set the app bar background to indicate proxying through Orbot is enabled.
4361                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4362                     appBarLayout.setBackgroundResource(R.color.blue_50);
4363                 } else {
4364                     appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4365                 }
4366
4367                 // Check to see if I2P is installed.
4368                 try {
4369                     // Get the package manager.
4370                     PackageManager packageManager = getPackageManager();
4371
4372                     // Check to see if I2P is in the list.  This will throw an error and drop to the catch section if it isn't installed.
4373                     packageManager.getPackageInfo("net.i2p.android.router", 0);
4374                 } catch (PackageManager.NameNotFoundException exception) {  // I2P is not installed.
4375                     // Sow the I2P not installed dialog if it is not already displayed.
4376                     if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
4377                         // Get a handle for the waiting for proxy alert dialog.
4378                         DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
4379
4380                         // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
4381                         try {
4382                             // Display the I2P not installed alert dialog.
4383                             i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
4384                         } catch (Exception i2pNotInstalledException) {
4385                             // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
4386                             pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
4387                         }
4388                     }
4389                 }
4390                 break;
4391
4392             case ProxyHelper.CUSTOM:
4393                 // Set the app bar background to indicate proxying through Orbot is enabled.
4394                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4395                     appBarLayout.setBackgroundResource(R.color.blue_50);
4396                 } else {
4397                     appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4398                 }
4399                 break;
4400         }
4401
4402         // Reload the WebViews if requested and not waiting for the proxy.
4403         if (reloadWebViews && !waitingForProxy) {
4404             // Reload the WebViews.
4405             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4406                 // Get the WebView tab fragment.
4407                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4408
4409                 // Get the fragment view.
4410                 View fragmentView = webViewTabFragment.getView();
4411
4412                 // Only reload the WebViews if they exist.
4413                 if (fragmentView != null) {
4414                     // Get the nested scroll WebView from the tab fragment.
4415                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4416
4417                     // Reload the WebView.
4418                     nestedScrollWebView.reload();
4419                 }
4420             }
4421         }
4422     }
4423
4424     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4425         // Only update the privacy icons if the options menu and the current WebView have already been populated.
4426         if ((optionsMenu != null) && (currentWebView != null)) {
4427             // Update the privacy icon.
4428             if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is enabled.
4429                 optionsPrivacyMenuItem.setIcon(R.drawable.javascript_enabled);
4430             } else if (currentWebView.getAcceptCookies()) {  // JavaScript is disabled but cookies are enabled.
4431                 optionsPrivacyMenuItem.setIcon(R.drawable.warning);
4432             } else {  // All the dangerous features are disabled.
4433                 optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode);
4434             }
4435
4436             // Get the current theme status.
4437             int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4438
4439             // Update the cookies icon.
4440             if (currentWebView.getAcceptCookies()) {  // Cookies are enabled.
4441                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4442             } else {  // Cookies are disabled.
4443                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4444                     optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_day);
4445                 } else {
4446                     optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled_night);
4447                 }
4448             }
4449
4450             // Update the refresh icon.
4451             if (optionsRefreshMenuItem.getTitle() == getString(R.string.refresh)) {  // The refresh icon is displayed.
4452                 // Set the icon according to the theme.
4453                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4454                     optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
4455                 } else {
4456                     optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
4457                 }
4458             } else {  // The stop icon is displayed.
4459                 // Set the icon according to the theme.
4460                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4461                     optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
4462                 } else {
4463                     optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
4464                 }
4465             }
4466
4467             // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar.
4468             if (runInvalidateOptionsMenu) {
4469                 invalidateOptionsMenu();
4470             }
4471         }
4472     }
4473
4474     private void highlightUrlText() {
4475         // Only highlight the URL text if the box is not currently selected.
4476         if (!urlEditText.hasFocus()) {
4477             // Get the URL string.
4478             String urlString = urlEditText.getText().toString();
4479
4480             // Highlight the URL according to the protocol.
4481             if (urlString.startsWith("file://") || urlString.startsWith("content://")) {  // This is a file or content URL.
4482                 // De-emphasize everything before the file name.
4483                 urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4484             } else {  // This is a web URL.
4485                 // Get the index of the `/` immediately after the domain name.
4486                 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4487
4488                 // Create a base URL string.
4489                 String baseUrl;
4490
4491                 // Get the base URL.
4492                 if (endOfDomainName > 0) {  // There is at least one character after the base URL.
4493                     // Get the base URL.
4494                     baseUrl = urlString.substring(0, endOfDomainName);
4495                 } else {  // There are no characters after the base URL.
4496                     // Set the base URL to be the entire URL string.
4497                     baseUrl = urlString;
4498                 }
4499
4500                 // Get the index of the last `.` in the domain.
4501                 int lastDotIndex = baseUrl.lastIndexOf(".");
4502
4503                 // Get the index of the penultimate `.` in the domain.
4504                 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4505
4506                 // Markup the beginning of the URL.
4507                 if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
4508                     urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4509
4510                     // De-emphasize subdomains.
4511                     if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
4512                         urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4513                     }
4514                 } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
4515                     if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
4516                         // De-emphasize the protocol and the additional subdomains.
4517                         urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4518                     } else {  // There is only one subdomain in the domain name.
4519                         // De-emphasize only the protocol.
4520                         urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4521                     }
4522                 }
4523
4524                 // De-emphasize the text after the domain name.
4525                 if (endOfDomainName > 0) {
4526                     urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4527                 }
4528             }
4529         }
4530     }
4531
4532     private void loadBookmarksFolder() {
4533         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4534         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4535
4536         // Populate the bookmarks cursor adapter.
4537         bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4538             @Override
4539             public View newView(Context context, Cursor cursor, ViewGroup parent) {
4540                 // Inflate the individual item layout.
4541                 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4542             }
4543
4544             @Override
4545             public void bindView(View view, Context context, Cursor cursor) {
4546                 // Get handles for the views.
4547                 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4548                 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4549
4550                 // Get the favorite icon byte array from the cursor.
4551                 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4552
4553                 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4554                 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4555
4556                 // Display the bitmap in `bookmarkFavoriteIcon`.
4557                 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4558
4559                 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4560                 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4561                 bookmarkNameTextView.setText(bookmarkNameString);
4562
4563                 // Make the font bold for folders.
4564                 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4565                     bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4566                 } else {  // Reset the font to default for normal bookmarks.
4567                     bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4568                 }
4569             }
4570         };
4571
4572         // Get a handle for the bookmarks list view.
4573         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
4574
4575         // Populate the list view with the adapter.
4576         bookmarksListView.setAdapter(bookmarksCursorAdapter);
4577
4578         // Get a handle for the bookmarks title text view.
4579         TextView bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
4580
4581         // Set the bookmarks drawer title.
4582         if (currentBookmarksFolder.isEmpty()) {
4583             bookmarksTitleTextView.setText(R.string.bookmarks);
4584         } else {
4585             bookmarksTitleTextView.setText(currentBookmarksFolder);
4586         }
4587     }
4588
4589     private void openWithApp(String url) {
4590         // Create an open with app intent with `ACTION_VIEW`.
4591         Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
4592
4593         // Set the URI but not the MIME type.  This should open all available apps.
4594         openWithAppIntent.setData(Uri.parse(url));
4595
4596         // Flag the intent to open in a new task.
4597         openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4598
4599         // Try the intent.
4600         try {
4601             // Show the chooser.
4602             startActivity(openWithAppIntent);
4603         } catch (ActivityNotFoundException exception) {  // There are no apps available to open the URL.
4604             // Show a snackbar with the error.
4605             Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
4606         }
4607     }
4608
4609     private void openWithBrowser(String url) {
4610         // Create an open with browser intent with `ACTION_VIEW`.
4611         Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
4612
4613         // Set the URI and the MIME type.  `"text/html"` should load browser options.
4614         openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
4615
4616         // Flag the intent to open in a new task.
4617         openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4618
4619         // Try the intent.
4620         try {
4621             // Show the chooser.
4622             startActivity(openWithBrowserIntent);
4623         } catch (ActivityNotFoundException exception) {  // There are no browsers available to open the URL.
4624             // Show a snackbar with the error.
4625             Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
4626         }
4627     }
4628
4629     private String sanitizeUrl(String url) {
4630         // Sanitize Google Analytics.
4631         if (sanitizeGoogleAnalytics) {
4632             // Remove `?utm_`.
4633             if (url.contains("?utm_")) {
4634                 url = url.substring(0, url.indexOf("?utm_"));
4635             }
4636
4637             // Remove `&utm_`.
4638             if (url.contains("&utm_")) {
4639                 url = url.substring(0, url.indexOf("&utm_"));
4640             }
4641         }
4642
4643         // Sanitize Facebook Click IDs.
4644         if (sanitizeFacebookClickIds) {
4645             // Remove `?fbclid=`.
4646             if (url.contains("?fbclid=")) {
4647                 url = url.substring(0, url.indexOf("?fbclid="));
4648             }
4649
4650             // Remove `&fbclid=`.
4651             if (url.contains("&fbclid=")) {
4652                 url = url.substring(0, url.indexOf("&fbclid="));
4653             }
4654
4655             // Remove `?fbadid=`.
4656             if (url.contains("?fbadid=")) {
4657                 url = url.substring(0, url.indexOf("?fbadid="));
4658             }
4659
4660             // Remove `&fbadid=`.
4661             if (url.contains("&fbadid=")) {
4662                 url = url.substring(0, url.indexOf("&fbadid="));
4663             }
4664         }
4665
4666         // Sanitize Twitter AMP redirects.
4667         if (sanitizeTwitterAmpRedirects) {
4668             // Remove `?amp=1`.
4669             if (url.contains("?amp=1")) {
4670                 url = url.substring(0, url.indexOf("?amp=1"));
4671             }
4672         }
4673
4674         // Return the sanitized URL.
4675         return url;
4676     }
4677
4678     public void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
4679         // Store the blocklists.
4680         easyList = combinedBlocklists.get(0);
4681         easyPrivacy = combinedBlocklists.get(1);
4682         fanboysAnnoyanceList = combinedBlocklists.get(2);
4683         fanboysSocialList = combinedBlocklists.get(3);
4684         ultraList = combinedBlocklists.get(4);
4685         ultraPrivacy = combinedBlocklists.get(5);
4686
4687         // Check to see if the activity has been restarted with a saved state.
4688         if ((savedStateArrayList == null) || (savedStateArrayList.size() == 0)) {  // The activity has not been restarted or it was restarted on start to force the night theme.
4689             // Add the first tab.
4690             addNewTab("", true);
4691         } else {  // The activity has been restarted.
4692             // Restore each tab.  Once the minimum API >= 24, a `forEach()` command can be used.
4693             for (int i = 0; i < savedStateArrayList.size(); i++) {
4694                 // Add a new tab.
4695                 tabLayout.addTab(tabLayout.newTab());
4696
4697                 // Get the new tab.
4698                 TabLayout.Tab newTab = tabLayout.getTabAt(i);
4699
4700                 // Remove the lint warning below that the current tab might be null.
4701                 assert newTab != null;
4702
4703                 // Set a custom view on the new tab.
4704                 newTab.setCustomView(R.layout.tab_custom_view);
4705
4706                 // Add the new page.
4707                 webViewPagerAdapter.restorePage(savedStateArrayList.get(i), savedNestedScrollWebViewStateArrayList.get(i));
4708             }
4709
4710             // Reset the saved state variables.
4711             savedStateArrayList = null;
4712             savedNestedScrollWebViewStateArrayList = null;
4713
4714             // Restore the selected tab position.
4715             if (savedTabPosition == 0) {  // The first tab is selected.
4716                 // Set the first page as the current WebView.
4717                 setCurrentWebView(0);
4718             } else {  // the first tab is not selected.
4719                 // Move to the selected tab.
4720                 webViewPager.setCurrentItem(savedTabPosition);
4721             }
4722
4723             // Get the intent that started the app.
4724             Intent intent = getIntent();
4725
4726             // Reset the intent.  This prevents a duplicate tab from being created on restart.
4727             setIntent(new Intent());
4728
4729             // Get the information from the intent.
4730             String intentAction = intent.getAction();
4731             Uri intentUriData = intent.getData();
4732             String intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT);
4733
4734             // Determine if this is a web search.
4735             boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
4736
4737             // 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.
4738             if (intentUriData != null || intentStringExtra != null || isWebSearch) {
4739                 // Get the shared preferences.
4740                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4741
4742                 // Create a URL string.
4743                 String url;
4744
4745                 // If the intent action is a web search, perform the search.
4746                 if (isWebSearch) {  // The intent is a web search.
4747                     // Create an encoded URL string.
4748                     String encodedUrlString;
4749
4750                     // Sanitize the search input and convert it to a search.
4751                     try {
4752                         encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
4753                     } catch (UnsupportedEncodingException exception) {
4754                         encodedUrlString = "";
4755                     }
4756
4757                     // Add the base search URL.
4758                     url = searchURL + encodedUrlString;
4759                 } else if (intentUriData != null) {  // The intent contains a URL formatted as a URI.
4760                     // Set the intent data as the URL.
4761                     url = intentUriData.toString();
4762                 } else {  // The intent contains a string, which might be a URL.
4763                     // Set the intent string as the URL.
4764                     url = intentStringExtra;
4765                 }
4766
4767                 // Add a new tab if specified in the preferences.
4768                 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
4769                     // Set the loading new intent flag.
4770                     loadingNewIntent = true;
4771
4772                     // Add a new tab.
4773                     addNewTab(url, true);
4774                 } else {  // Load the URL in the current tab.
4775                     // Make it so.
4776                     loadUrl(currentWebView, url);
4777                 }
4778             }
4779         }
4780     }
4781
4782     public void addTab(View view) {
4783         // Add a new tab with a blank URL.
4784         addNewTab("", true);
4785     }
4786
4787     private void addNewTab(String url, boolean moveToTab) {
4788         // Clear the focus from the URL edit text, so that it will be populated with the information from the new tab.
4789         urlEditText.clearFocus();
4790
4791         // Get the new page number.  The page numbers are 0 indexed, so the new page number will match the current count.
4792         int newTabNumber = tabLayout.getTabCount();
4793
4794         // Add a new tab.
4795         tabLayout.addTab(tabLayout.newTab());
4796
4797         // Get the new tab.
4798         TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber);
4799
4800         // Remove the lint warning below that the current tab might be null.
4801         assert newTab != null;
4802
4803         // Set a custom view on the new tab.
4804         newTab.setCustomView(R.layout.tab_custom_view);
4805
4806         // Add the new WebView page.
4807         webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab);
4808
4809         // Show the app bar if it is at the bottom of the screen and the new tab is taking focus.
4810         if (bottomAppBar && moveToTab) {
4811             appBarLayout.setVisibility(View.VISIBLE);
4812         }
4813     }
4814
4815     public void closeTab(View view) {
4816         // Run the command according to the number of tabs.
4817         if (tabLayout.getTabCount() > 1) {  // There is more than one tab open.
4818             // Close the current tab.
4819             closeCurrentTab();
4820         } else {  // There is only one tab open.
4821             clearAndExit();
4822         }
4823     }
4824
4825     private void closeCurrentTab() {
4826         // Get the current tab number.
4827         int currentTabNumber = tabLayout.getSelectedTabPosition();
4828
4829         // Delete the current tab.
4830         tabLayout.removeTabAt(currentTabNumber);
4831
4832         // Delete the current page.  If the selected page number did not change during the delete (because the newly selected tab has has same number as the previously deleted tab), it will return true,
4833         // meaning that the current WebView must be reset.  Otherwise it will happen automatically as the selected tab number changes.
4834         if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
4835             setCurrentWebView(currentTabNumber);
4836         }
4837     }
4838
4839     private void exitFullScreenVideo() {
4840         // Re-enable the screen timeout.
4841         fullScreenVideoFrameLayout.setKeepScreenOn(false);
4842
4843         // Unset the full screen video flag.
4844         displayingFullScreenVideo = false;
4845
4846         // Remove all the views from the full screen video frame layout.
4847         fullScreenVideoFrameLayout.removeAllViews();
4848
4849         // Hide the full screen video frame layout.
4850         fullScreenVideoFrameLayout.setVisibility(View.GONE);
4851
4852         // Enable the sliding drawers.
4853         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
4854
4855         // Show the main content relative layout.
4856         mainContentRelativeLayout.setVisibility(View.VISIBLE);
4857
4858         // Apply the appropriate full screen mode flags.
4859         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
4860             // Hide the app bar if specified.
4861             if (hideAppBar) {
4862                 // Hide the tab linear layout.
4863                 tabsLinearLayout.setVisibility(View.GONE);
4864
4865                 // Hide the action bar.
4866                 actionBar.hide();
4867             }
4868
4869             // Hide the banner ad in the free flavor.
4870             if (BuildConfig.FLAVOR.contentEquals("free")) {
4871                 // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
4872                 View adView = findViewById(R.id.adview);
4873
4874                 // Hide the banner ad.
4875                 AdHelper.hideAd(adView);
4876             }
4877
4878             /* Hide the system bars.
4879              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4880              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4881              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4882              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4883              */
4884             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
4885                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4886         } else {  // Switch to normal viewing mode.
4887             // Remove the `SYSTEM_UI` flags from the root frame layout.
4888             rootFrameLayout.setSystemUiVisibility(0);
4889         }
4890
4891         // Reload the ad for the free flavor if not in full screen mode.
4892         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
4893             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
4894             View adView = findViewById(R.id.adview);
4895
4896             // Reload the ad.
4897             AdHelper.loadAd(adView, this, this, getString(R.string.ad_unit_id));
4898         }
4899     }
4900
4901     private void clearAndExit() {
4902         // Get a handle for the shared preferences.
4903         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4904
4905         // Close the bookmarks cursor and database.
4906         bookmarksCursor.close();
4907         bookmarksDatabaseHelper.close();
4908
4909         // Get the status of the clear everything preference.
4910         boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
4911
4912         // Get a handle for the runtime.
4913         Runtime runtime = Runtime.getRuntime();
4914
4915         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
4916         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
4917         String privateDataDirectoryString = getApplicationInfo().dataDir;
4918
4919         // Clear cookies.
4920         if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
4921             // The command to remove cookies changed slightly in API 21.
4922             if (Build.VERSION.SDK_INT >= 21) {
4923                 CookieManager.getInstance().removeAllCookies(null);
4924             } else {
4925                 CookieManager.getInstance().removeAllCookie();
4926             }
4927
4928             // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4929             try {
4930                 // Two commands must be used because `Runtime.exec()` does not like `*`.
4931                 Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
4932                 Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
4933
4934                 // Wait until the processes have finished.
4935                 deleteCookiesProcess.waitFor();
4936                 deleteCookiesJournalProcess.waitFor();
4937             } catch (Exception exception) {
4938                 // Do nothing if an error is thrown.
4939             }
4940         }
4941
4942         // Clear DOM storage.
4943         if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
4944             // Ask `WebStorage` to clear the DOM storage.
4945             WebStorage webStorage = WebStorage.getInstance();
4946             webStorage.deleteAllData();
4947
4948             // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4949             try {
4950                 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4951                 Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
4952
4953                 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
4954                 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
4955                 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
4956                 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
4957                 Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
4958
4959                 // Wait until the processes have finished.
4960                 deleteLocalStorageProcess.waitFor();
4961                 deleteIndexProcess.waitFor();
4962                 deleteQuotaManagerProcess.waitFor();
4963                 deleteQuotaManagerJournalProcess.waitFor();
4964                 deleteDatabaseProcess.waitFor();
4965             } catch (Exception exception) {
4966                 // Do nothing if an error is thrown.
4967             }
4968         }
4969
4970         // Clear form data if the API < 26.
4971         if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
4972             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
4973             webViewDatabase.clearFormData();
4974
4975             // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4976             try {
4977                 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
4978                 Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
4979                 Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
4980
4981                 // Wait until the processes have finished.
4982                 deleteWebDataProcess.waitFor();
4983                 deleteWebDataJournalProcess.waitFor();
4984             } catch (Exception exception) {
4985                 // Do nothing if an error is thrown.
4986             }
4987         }
4988
4989         // Clear the logcat.
4990         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_logcat_key), true)) {
4991             try {
4992                 // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
4993                 Process process = Runtime.getRuntime().exec("logcat -b all -c");
4994
4995                 // Wait for the process to finish.
4996                 process.waitFor();
4997             } catch (IOException|InterruptedException exception) {
4998                 // Do nothing.
4999             }
5000         }
5001
5002         // Clear the cache.
5003         if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
5004             // Clear the cache from each WebView.
5005             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
5006                 // Get the WebView tab fragment.
5007                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
5008
5009                 // Get the WebView fragment view.
5010                 View webViewFragmentView = webViewTabFragment.getView();
5011
5012                 // Only clear the cache if the WebView exists.
5013                 if (webViewFragmentView != null) {
5014                     // Get the nested scroll WebView from the tab fragment.
5015                     NestedScrollWebView nestedScrollWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview);
5016
5017                     // Clear the cache for this WebView.
5018                     nestedScrollWebView.clearCache(true);
5019                 }
5020             }
5021
5022             // Manually delete the cache directories.
5023             try {
5024                 // Delete the main cache directory.
5025                 Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache");
5026
5027                 // Delete the secondary `Service Worker` cache directory.
5028                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
5029                 Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
5030
5031                 // Wait until the processes have finished.
5032                 deleteCacheProcess.waitFor();
5033                 deleteServiceWorkerProcess.waitFor();
5034             } catch (Exception exception) {
5035                 // Do nothing if an error is thrown.
5036             }
5037         }
5038
5039         // Wipe out each WebView.
5040         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
5041             // Get the WebView tab fragment.
5042             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
5043
5044             // Get the WebView frame layout.
5045             FrameLayout webViewFrameLayout = (FrameLayout) webViewTabFragment.getView();
5046
5047             // Only wipe out the WebView if it exists.
5048             if (webViewFrameLayout != null) {
5049                 // Get the nested scroll WebView from the tab fragment.
5050                 NestedScrollWebView nestedScrollWebView = webViewFrameLayout.findViewById(R.id.nestedscroll_webview);
5051
5052                 // Clear SSL certificate preferences for this WebView.
5053                 nestedScrollWebView.clearSslPreferences();
5054
5055                 // Clear the back/forward history for this WebView.
5056                 nestedScrollWebView.clearHistory();
5057
5058                 // Remove all the views from the frame layout.
5059                 webViewFrameLayout.removeAllViews();
5060
5061                 // Destroy the internal state of the WebView.
5062                 nestedScrollWebView.destroy();
5063             }
5064         }
5065
5066         // Clear the custom headers.
5067         customHeaders.clear();
5068
5069         // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
5070         // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
5071         if (clearEverything) {
5072             try {
5073                 // Delete the folder.
5074                 Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
5075
5076                 // Wait until the process has finished.
5077                 deleteAppWebviewProcess.waitFor();
5078             } catch (Exception exception) {
5079                 // Do nothing if an error is thrown.
5080             }
5081         }
5082
5083         // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
5084         if (Build.VERSION.SDK_INT >= 21) {
5085             finishAndRemoveTask();
5086         } else {
5087             finish();
5088         }
5089
5090         // Remove the terminated program from RAM.  The status code is `0`.
5091         System.exit(0);
5092     }
5093
5094     public void bookmarksBack(View view) {
5095         if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
5096             // close the bookmarks drawer.
5097             drawerLayout.closeDrawer(GravityCompat.END);
5098         } else {  // A subfolder is displayed.
5099             // Place the former parent folder in `currentFolder`.
5100             currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
5101
5102             // Load the new folder.
5103             loadBookmarksFolder();
5104         }
5105     }
5106
5107     private void setCurrentWebView(int pageNumber) {
5108         // Stop the swipe to refresh indicator if it is running
5109         swipeRefreshLayout.setRefreshing(false);
5110
5111         // Get the WebView tab fragment.
5112         WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(pageNumber);
5113
5114         // Get the fragment view.
5115         View webViewFragmentView = webViewTabFragment.getView();
5116
5117         // Set the current WebView if the fragment view is not null.
5118         if (webViewFragmentView != null) {  // The fragment has been populated.
5119             // Store the current WebView.
5120             currentWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview);
5121
5122             // Update the status of swipe to refresh.
5123             if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
5124                 // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.  It is updated every time the scroll changes.
5125                 swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
5126             } else {  // Swipe to refresh is disabled.
5127                 // Disable the swipe refresh layout.
5128                 swipeRefreshLayout.setEnabled(false);
5129             }
5130
5131             // Get a handle for the cookie manager.
5132             CookieManager cookieManager = CookieManager.getInstance();
5133
5134             // Set the cookie status.
5135             cookieManager.setAcceptCookie(currentWebView.getAcceptCookies());
5136
5137             // Update the privacy icons.  `true` redraws the icons in the app bar.
5138             updatePrivacyIcons(true);
5139
5140             // Get a handle for the input method manager.
5141             InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
5142
5143             // Remove the lint warning below that the input method manager might be null.
5144             assert inputMethodManager != null;
5145
5146             // Get the current URL.
5147             String url = currentWebView.getUrl();
5148
5149             // Update the URL edit text if not loading a new intent.  Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
5150             if (!loadingNewIntent) {  // A new intent is not being loaded.
5151                 if ((url == null) || url.equals("about:blank")) {  // The WebView is blank.
5152                     // Display the hint in the URL edit text.
5153                     urlEditText.setText("");
5154
5155                     // Request focus for the URL text box.
5156                     urlEditText.requestFocus();
5157
5158                     // Display the keyboard.
5159                     inputMethodManager.showSoftInput(urlEditText, 0);
5160                 } else {  // The WebView has a loaded URL.
5161                     // Clear the focus from the URL text box.
5162                     urlEditText.clearFocus();
5163
5164                     // Hide the soft keyboard.
5165                     inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
5166
5167                     // Display the current URL in the URL text box.
5168                     urlEditText.setText(url);
5169
5170                     // Highlight the URL text.
5171                     highlightUrlText();
5172                 }
5173             } else {  // A new intent is being loaded.
5174                 // Reset the loading new intent tracker.
5175                 loadingNewIntent = false;
5176             }
5177
5178             // Set the background to indicate the domain settings status.
5179             if (currentWebView.getDomainSettingsApplied()) {
5180                 // Get the current theme status.
5181                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
5182
5183                 // Set a green background on the URL relative layout to indicate that custom domain settings are being used.
5184                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
5185                     urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_light_green, null));
5186                 } else {
5187                     urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.url_bar_background_dark_blue, null));
5188                 }
5189             } else {
5190                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
5191             }
5192         } else {  // The fragment has not been populated.  Try again in 100 milliseconds.
5193             // Create a handler to set the current WebView.
5194             Handler setCurrentWebViewHandler = new Handler();
5195
5196             // Create a runnable to set the current WebView.
5197             Runnable setCurrentWebWebRunnable = () -> {
5198                 // Set the current WebView.
5199                 setCurrentWebView(pageNumber);
5200             };
5201
5202             // Try setting the current WebView again after 100 milliseconds.
5203             setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100);
5204         }
5205     }
5206
5207     @Override
5208     public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState) {
5209         // Get a handle for the shared preferences.
5210         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
5211
5212         // Get the WebView theme.
5213         String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
5214
5215         // Get the WebView theme entry values string array.
5216         String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values);
5217
5218         // Apply the WebView theme if supported by the installed WebView.
5219         if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
5220             // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
5221             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
5222                 // Turn off the WebView dark mode.
5223                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
5224
5225                 // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
5226                 // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
5227                 nestedScrollWebView.setVisibility(View.VISIBLE);
5228             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
5229                 // Turn on the WebView dark mode.
5230                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
5231             } else {  // The system default theme is selected.
5232                 // Get the current system theme status.
5233                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
5234
5235                 // Set the WebView theme according to the current system theme status.
5236                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
5237                     // Turn off the WebView dark mode.
5238                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
5239
5240                     // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
5241                     // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
5242                     nestedScrollWebView.setVisibility(View.VISIBLE);
5243                 } else {  // The system is in night mode.
5244                     // Turn on the WebView dark mode.
5245                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
5246                 }
5247             }
5248         }
5249
5250         // Get a handle for the activity
5251         Activity activity = this;
5252
5253         // Get a handle for the input method manager.
5254         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
5255
5256         // Instantiate the blocklist helper.
5257         BlocklistHelper blocklistHelper = new BlocklistHelper();
5258
5259         // Remove the lint warning below that the input method manager might be null.
5260         assert inputMethodManager != null;
5261
5262         // Initialize the favorite icon.
5263         nestedScrollWebView.initializeFavoriteIcon();
5264
5265         // Set the app bar scrolling.
5266         nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
5267
5268         // Allow pinch to zoom.
5269         nestedScrollWebView.getSettings().setBuiltInZoomControls(true);
5270
5271         // Hide zoom controls.
5272         nestedScrollWebView.getSettings().setDisplayZoomControls(false);
5273
5274         // Don't allow mixed content (HTTP and HTTPS) on the same website.
5275         if (Build.VERSION.SDK_INT >= 21) {
5276             nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
5277         }
5278
5279         // Set the WebView to load in overview mode (zoomed out to the maximum width).
5280         nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
5281
5282         // Explicitly disable geolocation.
5283         nestedScrollWebView.getSettings().setGeolocationEnabled(false);
5284
5285         // Allow loading of file:// URLs.  This is necessary for opening MHT web archives, which are copies into a temporary cache location.
5286         nestedScrollWebView.getSettings().setAllowFileAccess(true);
5287
5288         // Create a double-tap gesture detector to toggle full-screen mode.
5289         GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
5290             // Override `onDoubleTap()`.  All other events are handled using the default settings.
5291             @Override
5292             public boolean onDoubleTap(MotionEvent event) {
5293                 if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
5294                     // Toggle the full screen browsing mode tracker.
5295                     inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
5296
5297                     // Toggle the full screen browsing mode.
5298                     if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
5299                         // Hide the app bar if specified.
5300                         if (hideAppBar) {
5301                             // Close the find on page bar if it is visible.
5302                             closeFindOnPage(null);
5303
5304                             // Hide the tab linear layout.
5305                             tabsLinearLayout.setVisibility(View.GONE);
5306
5307                             // Hide the action bar.
5308                             actionBar.hide();
5309
5310                             // Set layout and scrolling parameters if the app bar is at the top of the screen.
5311                             if (!bottomAppBar) {
5312                                 // Check to see if the app bar is normally scrolled.
5313                                 if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
5314                                     // Get the swipe refresh layout parameters.
5315                                     CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
5316
5317                                     // Remove the off-screen scrolling layout.
5318                                     swipeRefreshLayoutParams.setBehavior(null);
5319                                 } else {  // The app bar is not scrolled when it is displayed.
5320                                     // Remove the padding from the top of the swipe refresh layout.
5321                                     swipeRefreshLayout.setPadding(0, 0, 0, 0);
5322
5323                                     // The swipe refresh circle must be moved above the now removed status bar location.
5324                                     swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
5325                                 }
5326                             }
5327                         }
5328
5329                         // Hide the banner ad in the free flavor.
5330                         if (BuildConfig.FLAVOR.contentEquals("free")) {
5331                             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
5332                             View adView = findViewById(R.id.adview);
5333
5334                             // Hide the banner ad.
5335                             AdHelper.hideAd(adView);
5336                         }
5337
5338                         /* Hide the system bars.
5339                          * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5340                          * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5341                          * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5342                          * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5343                          */
5344                         rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5345                                 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5346                     } else {  // Switch to normal viewing mode.
5347                         // Show the app bar if it was hidden.
5348                         if (hideAppBar) {
5349                             // Show the tab linear layout.
5350                             tabsLinearLayout.setVisibility(View.VISIBLE);
5351
5352                             // Show the action bar.
5353                             actionBar.show();
5354
5355                             // Set layout and scrolling parameters if the app bar is at the top of the screen.
5356                             if (!bottomAppBar) {
5357                                 // Check to see if the app bar is normally scrolled.
5358                                 if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
5359                                     // Get the swipe refresh layout parameters.
5360                                     CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
5361
5362                                     // Add the off-screen scrolling layout.
5363                                     swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
5364                                 } else {  // The app bar is not scrolled when it is displayed.
5365                                     // The swipe refresh layout must be manually moved below the app bar layout.
5366                                     swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
5367
5368                                     // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5369                                     swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
5370                                 }
5371                             }
5372                         }
5373
5374                         // Show the banner ad in the free flavor.
5375                         if (BuildConfig.FLAVOR.contentEquals("free")) {
5376                             // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
5377                             View adView = findViewById(R.id.adview);
5378
5379                             // Reload the ad.  `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
5380                             AdHelper.loadAd(adView, getApplicationContext(), activity, getString(R.string.ad_unit_id));
5381                         }
5382
5383                         // Remove the `SYSTEM_UI` flags from the root frame layout.
5384                         rootFrameLayout.setSystemUiVisibility(0);
5385                     }
5386
5387                     // Consume the double-tap.
5388                     return true;
5389                 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
5390                     return false;
5391                 }
5392             }
5393         });
5394
5395         // Pass all touch events on the WebView through the double-tap gesture detector.
5396         nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> {
5397             // Call `performClick()` on the view, which is required for accessibility.
5398             view.performClick();
5399
5400             // Send the event to the gesture detector.
5401             return doubleTapGestureDetector.onTouchEvent(event);
5402         });
5403
5404         // Register the WebView for a context menu.  This is used to see link targets and download images.
5405         registerForContextMenu(nestedScrollWebView);
5406
5407         // Allow the downloading of files.
5408         nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
5409             // Check the download preference.
5410             if (downloadWithExternalApp) {  // Download with an external app.
5411                 downloadUrlWithExternalApp(downloadUrl);
5412             } else {  // Handle the download inside of Privacy Browser.
5413                 // Define a formatted file size string.
5414                 String formattedFileSizeString;
5415
5416                 // Process the content length if it contains data.
5417                 if (contentLength > 0) {  // The content length is greater than 0.
5418                     // Format the content length as a string.
5419                     formattedFileSizeString = NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes);
5420                 } else {  // The content length is not greater than 0.
5421                     // Set the formatted file size string to be `unknown size`.
5422                     formattedFileSizeString = getString(R.string.unknown_size);
5423                 }
5424
5425                 // Get the file name from the content disposition.
5426                 String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
5427
5428                 // Instantiate the save dialog.
5429                 DialogFragment saveDialogFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_URL, downloadUrl, formattedFileSizeString, fileNameString, userAgent,
5430                         nestedScrollWebView.getAcceptCookies());
5431
5432                 // Try to show the dialog.  The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
5433                 try {
5434                     // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
5435                     saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
5436                 } catch (Exception exception) {  // The dialog could not be shown.
5437                     // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
5438                     pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog)));
5439                 }
5440             }
5441         });
5442
5443         // Update the find on page count.
5444         nestedScrollWebView.setFindListener(new WebView.FindListener() {
5445             // Get a handle for `findOnPageCountTextView`.
5446             final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
5447
5448             @Override
5449             public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
5450                 if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
5451                     // Set `findOnPageCountTextView` to `0/0`.
5452                     findOnPageCountTextView.setText(R.string.zero_of_zero);
5453                 } else if (isDoneCounting) {  // There are matches.
5454                     // `activeMatchOrdinal` is zero-based.
5455                     int activeMatch = activeMatchOrdinal + 1;
5456
5457                     // Build the match string.
5458                     String matchString = activeMatch + "/" + numberOfMatches;
5459
5460                     // Set `findOnPageCountTextView`.
5461                     findOnPageCountTextView.setText(matchString);
5462                 }
5463             }
5464         });
5465
5466         // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.  Also reinforce full screen browsing mode.
5467         // On API < 23, `getViewTreeObserver().addOnScrollChangedListener()` must be used, but it is a little bit buggy and appears to get garbage collected from time to time.
5468         if (Build.VERSION.SDK_INT >= 23) {
5469             nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
5470                 // Set the swipe to refresh status.
5471                 if (nestedScrollWebView.getSwipeToRefresh()) {
5472                     // Only enable swipe to refresh if the WebView is scrolled to the top.
5473                     swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
5474                 } else {
5475                     // Disable swipe to refresh.
5476                     swipeRefreshLayout.setEnabled(false);
5477                 }
5478
5479                 //  Set the visibility of the bottom app bar.
5480                 if (bottomAppBar && scrollAppBar && (Calendar.getInstance().getTimeInMillis() - lastScrollUpdate > 100)) {
5481                     if (scrollY - oldScrollY > 25) {  // The WebView was scrolled down.
5482                         appBarLayout.setVisibility(View.GONE);
5483                     } else if (scrollY - oldScrollY < -25) {  // The WebView was scrolled up.
5484                         appBarLayout.setVisibility(View.VISIBLE);
5485                     }
5486
5487                     // Update the last scroll update variable.  This prevents the app bar from flashing on and off at the bottom of the screen.
5488                     lastScrollUpdate = Calendar.getInstance().getTimeInMillis();
5489                 }
5490
5491                 // Reinforce the system UI visibility flags if in full screen browsing mode.
5492                 // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
5493                 if (inFullScreenBrowsingMode) {
5494                     /* Hide the system bars.
5495                      * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5496                      * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5497                      * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5498                      * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5499                      */
5500                     rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5501                             View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5502                 }
5503             });
5504         } else {
5505             nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
5506                 if (nestedScrollWebView.getSwipeToRefresh()) {
5507                     // Only enable swipe to refresh if the WebView is scrolled to the top.
5508                     swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
5509                 } else {
5510                     // Disable swipe to refresh.
5511                     swipeRefreshLayout.setEnabled(false);
5512                 }
5513
5514                 // Reinforce the system UI visibility flags if in full screen browsing mode.
5515                 // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
5516                 if (inFullScreenBrowsingMode) {
5517                     /* Hide the system bars.
5518                      * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5519                      * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5520                      * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5521                      * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5522                      */
5523                     rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5524                             View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5525                 }
5526             });
5527         }
5528
5529         // Set the web chrome client.
5530         nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
5531             // Update the progress bar when a page is loading.
5532             @Override
5533             public void onProgressChanged(WebView view, int progress) {
5534                 // Update the progress bar.
5535                 progressBar.setProgress(progress);
5536
5537                 // Set the visibility of the progress bar.
5538                 if (progress < 100) {
5539                     // Show the progress bar.
5540                     progressBar.setVisibility(View.VISIBLE);
5541                 } else {
5542                     // Hide the progress bar.
5543                     progressBar.setVisibility(View.GONE);
5544
5545                     //Stop the swipe to refresh indicator if it is running
5546                     swipeRefreshLayout.setRefreshing(false);
5547
5548                     // Make the current WebView visible.  If this is a new tab, the current WebView would have been created invisible in `webview_framelayout` to prevent a white background splash in night mode.
5549                     nestedScrollWebView.setVisibility(View.VISIBLE);
5550                 }
5551             }
5552
5553             // Set the favorite icon when it changes.
5554             @Override
5555             public void onReceivedIcon(WebView view, Bitmap icon) {
5556                 // Only update the favorite icon if the website has finished loading.
5557                 if (progressBar.getVisibility() == View.GONE) {
5558                     // Store the new favorite icon.
5559                     nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
5560
5561                     // Get the current page position.
5562                     int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5563
5564                     // Get the current tab.
5565                     TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
5566
5567                     // Check to see if the tab has been populated.
5568                     if (tab != null) {
5569                         // Get the custom view from the tab.
5570                         View tabView = tab.getCustomView();
5571
5572                         // Check to see if the custom tab view has been populated.
5573                         if (tabView != null) {
5574                             // Get the favorite icon image view from the tab.
5575                             ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview);
5576
5577                             // Display the favorite icon in the tab.
5578                             tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
5579                         }
5580                     }
5581                 }
5582             }
5583
5584             // Save a copy of the title when it changes.
5585             @Override
5586             public void onReceivedTitle(WebView view, String title) {
5587                 // Get the current page position.
5588                 int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5589
5590                 // Get the current tab.
5591                 TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
5592
5593                 // Only populate the title text view if the tab has been fully created.
5594                 if (tab != null) {
5595                     // Get the custom view from the tab.
5596                     View tabView = tab.getCustomView();
5597
5598                     // Only populate the title text view if the tab view has been fully populated.
5599                     if (tabView != null) {
5600                         // Get the title text view from the tab.
5601                         TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
5602
5603                         // Set the title according to the URL.
5604                         if (title.equals("about:blank")) {
5605                             // Set the title to indicate a new tab.
5606                             tabTitleTextView.setText(R.string.new_tab);
5607                         } else {
5608                             // Set the title as the tab text.
5609                             tabTitleTextView.setText(title);
5610                         }
5611                     }
5612                 }
5613             }
5614
5615             // Enter full screen video.
5616             @Override
5617             public void onShowCustomView(View video, CustomViewCallback callback) {
5618                 // Set the full screen video flag.
5619                 displayingFullScreenVideo = true;
5620
5621                 // Pause the ad if this is the free flavor.
5622                 if (BuildConfig.FLAVOR.contentEquals("free")) {
5623                     // Get a handle for the ad view.  This cannot be a class variable because it changes with each ad load.
5624                     View adView = findViewById(R.id.adview);
5625
5626                     // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
5627                     AdHelper.pauseAd(adView);
5628                 }
5629
5630                 // Hide the keyboard.
5631                 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
5632
5633                 // Hide the main content relative layout.
5634                 mainContentRelativeLayout.setVisibility(View.GONE);
5635
5636                 /* Hide the system bars.
5637                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5638                  * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5639                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5640                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5641                  */
5642                 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5643                         View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5644
5645                 // Disable the sliding drawers.
5646                 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
5647
5648                 // Add the video view to the full screen video frame layout.
5649                 fullScreenVideoFrameLayout.addView(video);
5650
5651                 // Show the full screen video frame layout.
5652                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
5653
5654                 // Disable the screen timeout while the video is playing.  YouTube does this automatically, but not all other videos do.
5655                 fullScreenVideoFrameLayout.setKeepScreenOn(true);
5656             }
5657
5658             // Exit full screen video.
5659             @Override
5660             public void onHideCustomView() {
5661                 // Exit the full screen video.
5662                 exitFullScreenVideo();
5663             }
5664
5665             // Upload files.
5666             @Override
5667             public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
5668                 // Show the file chooser if the device is running API >= 21.
5669                 if (Build.VERSION.SDK_INT >= 21) {
5670                     // Store the file path callback.
5671                     fileChooserCallback = filePathCallback;
5672
5673                     // Create an intent to open a chooser based on the file chooser parameters.
5674                     Intent fileChooserIntent = fileChooserParams.createIntent();
5675
5676                     // Get a handle for the package manager.
5677                     PackageManager packageManager = getPackageManager();
5678
5679                     // Check to see if the file chooser intent resolves to an installed package.
5680                     if (fileChooserIntent.resolveActivity(packageManager) != null) {  // The file chooser intent is fine.
5681                         // Start the file chooser intent.
5682                         startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
5683                     } else {  // The file chooser intent will cause a crash.
5684                         // Create a generic intent to open a chooser.
5685                         Intent genericFileChooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
5686
5687                         // Request an openable file.
5688                         genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE);
5689
5690                         // Set the file type to everything.
5691                         genericFileChooserIntent.setType("*/*");
5692
5693                         // Start the generic file chooser intent.
5694                         startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
5695                     }
5696                 }
5697                 return true;
5698             }
5699         });
5700
5701         nestedScrollWebView.setWebViewClient(new WebViewClient() {
5702             // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
5703             // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
5704             @Override
5705             public boolean shouldOverrideUrlLoading(WebView view, String url) {
5706                 // Sanitize the url.
5707                 url = sanitizeUrl(url);
5708
5709                 // Handle the URL according to the type.
5710                 if (url.startsWith("http")) {  // Load the URL in Privacy Browser.
5711                     // Load the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
5712                     loadUrl(nestedScrollWebView, url);
5713
5714                     // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
5715                     // Custom headers cannot be added if false is returned and the WebView handles the loading of the URL.
5716                     return true;
5717                 } else if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
5718                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
5719                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
5720
5721                     // Parse the url and set it as the data for the intent.
5722                     emailIntent.setData(Uri.parse(url));
5723
5724                     // Open the email program in a new task instead of as part of Privacy Browser.
5725                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5726
5727                     try {
5728                         // Make it so.
5729                         startActivity(emailIntent);
5730                     } catch (ActivityNotFoundException exception) {
5731                         // Display a snackbar.
5732                         Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
5733                     }
5734
5735
5736                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5737                     return true;
5738                 } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
5739                     // Open the dialer and load the phone number, but wait for the user to place the call.
5740                     Intent dialIntent = new Intent(Intent.ACTION_DIAL);
5741
5742                     // Add the phone number to the intent.
5743                     dialIntent.setData(Uri.parse(url));
5744
5745                     // Open the dialer in a new task instead of as part of Privacy Browser.
5746                     dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5747
5748                     try {
5749                         // Make it so.
5750                         startActivity(dialIntent);
5751                     } catch (ActivityNotFoundException exception) {
5752                         // Display a snackbar.
5753                         Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
5754                     }
5755
5756                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5757                     return true;
5758                 } else {  // Load a system chooser to select an app that can handle the URL.
5759                     // Open an app that can handle the URL.
5760                     Intent genericIntent = new Intent(Intent.ACTION_VIEW);
5761
5762                     // Add the URL to the intent.
5763                     genericIntent.setData(Uri.parse(url));
5764
5765                     // List all apps that can handle the URL instead of just opening the first one.
5766                     genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
5767
5768                     // Open the app in a new task instead of as part of Privacy Browser.
5769                     genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5770
5771                     // Start the app or display a snackbar if no app is available to handle the URL.
5772                     try {
5773                         startActivity(genericIntent);
5774                     } catch (ActivityNotFoundException exception) {
5775                         Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + "  " + url, Snackbar.LENGTH_SHORT).show();
5776                     }
5777
5778                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5779                     return true;
5780                 }
5781             }
5782
5783             // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
5784             @Override
5785             public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
5786                 // Check to see if the resource request is for the main URL.
5787                 if (url.equals(nestedScrollWebView.getCurrentUrl())) {
5788                     // `return null` loads the resource request, which should never be blocked if it is the main URL.
5789                     return null;
5790                 }
5791
5792                 // Wait until the blocklists have been populated.  When Privacy Browser is being resumed after having the process killed in the background it will try to load the URLs immediately.
5793                 while (ultraPrivacy == null) {
5794                     // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown.
5795                     synchronized (this) {
5796                         try {
5797                             // Check to see if the blocklists have been populated after 100 ms.
5798                             wait(100);
5799                         } catch (InterruptedException exception) {
5800                             // Do nothing.
5801                         }
5802                     }
5803                 }
5804
5805                 // Sanitize the URL.
5806                 url = sanitizeUrl(url);
5807
5808                 // Create an empty web resource response to be used if the resource request is blocked.
5809                 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
5810
5811                 // Reset the whitelist results tracker.
5812                 String[] whitelistResultStringArray = null;
5813
5814                 // Initialize the third party request tracker.
5815                 boolean isThirdPartyRequest = false;
5816
5817                 // Get the current URL.  `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
5818                 String currentBaseDomain = nestedScrollWebView.getCurrentDomainName();
5819
5820                 // Store a copy of the current domain for use in later requests.
5821                 String currentDomain = currentBaseDomain;
5822
5823                 // Nobody is happy when comparing null strings.
5824                 if ((currentBaseDomain != null) && (url != null)) {
5825                     // Convert the request URL to a URI.
5826                     Uri requestUri = Uri.parse(url);
5827
5828                     // Get the request host name.
5829                     String requestBaseDomain = requestUri.getHost();
5830
5831                     // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
5832                     if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
5833                         // Determine the current base domain.
5834                         while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
5835                             // Remove the first subdomain.
5836                             currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
5837                         }
5838
5839                         // Determine the request base domain.
5840                         while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
5841                             // Remove the first subdomain.
5842                             requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
5843                         }
5844
5845                         // Update the third party request tracker.
5846                         isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
5847                     }
5848                 }
5849
5850                 // Get the current WebView page position.
5851                 int webViewPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5852
5853                 // Determine if the WebView is currently displayed.
5854                 boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
5855
5856                 // Block third-party requests if enabled.
5857                 if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) {
5858                     // Add the result to the resource requests.
5859                     nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url});
5860
5861                     // Increment the blocked requests counters.
5862                     nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5863                     nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS);
5864
5865                     // Update the titles of the blocklist menu items if the WebView is currently displayed.
5866                     if (webViewDisplayed) {
5867                         // Updating the UI must be run from the UI thread.
5868                         activity.runOnUiThread(() -> {
5869                             // Update the menu item titles.
5870                             navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5871
5872                             // Update the options menu if it has been populated.
5873                             if (optionsMenu != null) {
5874                                 optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5875                                 optionsBlockAllThirdPartyRequestsMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
5876                                         getString(R.string.block_all_third_party_requests));
5877                             }
5878                         });
5879                     }
5880
5881                     // Return an empty web resource response.
5882                     return emptyWebResourceResponse;
5883                 }
5884
5885                 // Check UltraList if it is enabled.
5886                 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) {
5887                     // Check the URL against UltraList.
5888                     String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
5889
5890                     // Process the UltraList results.
5891                     if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraList's blacklist.
5892                         // Add the result to the resource requests.
5893                         nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
5894
5895                         // Increment the blocked requests counters.
5896                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5897                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRALIST);
5898
5899                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5900                         if (webViewDisplayed) {
5901                             // Updating the UI must be run from the UI thread.
5902                             activity.runOnUiThread(() -> {
5903                                 // Update the menu item titles.
5904                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5905
5906                                 // Update the options menu if it has been populated.
5907                                 if (optionsMenu != null) {
5908                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5909                                     optionsUltraListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
5910                                 }
5911                             });
5912                         }
5913
5914                         // The resource request was blocked.  Return an empty web resource response.
5915                         return emptyWebResourceResponse;
5916                     } else if (ultraListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraList's whitelist.
5917                         // Add a whitelist entry to the resource requests array.
5918                         nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
5919
5920                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
5921                         return null;
5922                     }
5923                 }
5924
5925                 // Check UltraPrivacy if it is enabled.
5926                 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) {
5927                     // Check the URL against UltraPrivacy.
5928                     String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
5929
5930                     // Process the UltraPrivacy results.
5931                     if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraPrivacy's blacklist.
5932                         // Add the result to the resource requests.
5933                         nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5934                                 ultraPrivacyResults[5]});
5935
5936                         // Increment the blocked requests counters.
5937                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5938                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRAPRIVACY);
5939
5940                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5941                         if (webViewDisplayed) {
5942                             // Updating the UI must be run from the UI thread.
5943                             activity.runOnUiThread(() -> {
5944                                 // Update the menu item titles.
5945                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5946
5947                                 // Update the options menu if it has been populated.
5948                                 if (optionsMenu != null) {
5949                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5950                                     optionsUltraPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
5951                                 }
5952                             });
5953                         }
5954
5955                         // The resource request was blocked.  Return an empty web resource response.
5956                         return emptyWebResourceResponse;
5957                     } else if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraPrivacy's whitelist.
5958                         // Add a whitelist entry to the resource requests array.
5959                         nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5960                                 ultraPrivacyResults[5]});
5961
5962                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
5963                         return null;
5964                     }
5965                 }
5966
5967                 // Check EasyList if it is enabled.
5968                 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) {
5969                     // Check the URL against EasyList.
5970                     String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
5971
5972                     // Process the EasyList results.
5973                     if (easyListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyList's blacklist.
5974                         // Add the result to the resource requests.
5975                         nestedScrollWebView.addResourceRequest(new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]});
5976
5977                         // Increment the blocked requests counters.
5978                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5979                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYLIST);
5980
5981                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5982                         if (webViewDisplayed) {
5983                             // Updating the UI must be run from the UI thread.
5984                             activity.runOnUiThread(() -> {
5985                                 // Update the menu item titles.
5986                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5987
5988                                 // Update the options menu if it has been populated.
5989                                 if (optionsMenu != null) {
5990                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5991                                     optionsEasyListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
5992                                 }
5993                             });
5994                         }
5995
5996                         // The resource request was blocked.  Return an empty web resource response.
5997                         return emptyWebResourceResponse;
5998                     } else if (easyListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched EasyList's whitelist.
5999                         // Update the whitelist result string array tracker.
6000                         whitelistResultStringArray = new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]};
6001                     }
6002                 }
6003
6004                 // Check EasyPrivacy if it is enabled.
6005                 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) {
6006                     // Check the URL against EasyPrivacy.
6007                     String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
6008
6009                     // Process the EasyPrivacy results.
6010                     if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyPrivacy's blacklist.
6011                         // Add the result to the resource requests.
6012                         nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4],
6013                                 easyPrivacyResults[5]});
6014
6015                         // Increment the blocked requests counters.
6016                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
6017                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYPRIVACY);
6018
6019                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
6020                         if (webViewDisplayed) {
6021                             // Updating the UI must be run from the UI thread.
6022                             activity.runOnUiThread(() -> {
6023                                 // Update the menu item titles.
6024                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
6025
6026                                 // Update the options menu if it has been populated.
6027                                 if (optionsMenu != null) {
6028                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
6029                                     optionsEasyPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
6030                                 }
6031                             });
6032                         }
6033
6034                         // The resource request was blocked.  Return an empty web resource response.
6035                         return emptyWebResourceResponse;
6036                     } else if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched EasyPrivacy's whitelist.
6037                         // Update the whitelist result string array tracker.
6038                         whitelistResultStringArray = new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]};
6039                     }
6040                 }
6041
6042                 // Check Fanboy’s Annoyance List if it is enabled.
6043                 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) {
6044                     // Check the URL against Fanboy's Annoyance List.
6045                     String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
6046
6047                     // Process the Fanboy's Annoyance List results.
6048                     if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched Fanboy's Annoyance List's blacklist.
6049                         // Add the result to the resource requests.
6050                         nestedScrollWebView.addResourceRequest(new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
6051                                 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]});
6052
6053                         // Increment the blocked requests counters.
6054                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
6055                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST);
6056
6057                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
6058                         if (webViewDisplayed) {
6059                             // Updating the UI must be run from the UI thread.
6060                             activity.runOnUiThread(() -> {
6061                                 // Update the menu item titles.
6062                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
6063
6064                                 // Update the options menu if it has been populated.
6065                                 if (optionsMenu != null) {
6066                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
6067                                     optionsFanboysAnnoyanceListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
6068                                             getString(R.string.fanboys_annoyance_list));
6069                                 }
6070                             });
6071                         }
6072
6073                         // The resource request was blocked.  Return an empty web resource response.
6074                         return emptyWebResourceResponse;
6075                     } else if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)){  // The resource request matched Fanboy's Annoyance List's whitelist.
6076                         // Update the whitelist result string array tracker.
6077                         whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
6078                                 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
6079                     }
6080                 } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
6081                     // Check the URL against Fanboy's Annoyance List.
6082                     String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
6083
6084                     // Process the Fanboy's Social Blocking List results.
6085                     if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched Fanboy's Social Blocking List's blacklist.
6086                         // Add the result to the resource requests.
6087                         nestedScrollWebView.addResourceRequest(new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
6088                                 fanboysSocialListResults[4], fanboysSocialListResults[5]});
6089
6090                         // Increment the blocked requests counters.
6091                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
6092                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST);
6093
6094                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
6095                         if (webViewDisplayed) {
6096                             // Updating the UI must be run from the UI thread.
6097                             activity.runOnUiThread(() -> {
6098                                 // Update the menu item titles.
6099                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
6100
6101                                 // Update the options menu if it has been populated.
6102                                 if (optionsMenu != null) {
6103                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
6104                                     optionsFanboysSocialBlockingListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
6105                                             getString(R.string.fanboys_social_blocking_list));
6106                                 }
6107                             });
6108                         }
6109
6110                         // The resource request was blocked.  Return an empty web resource response.
6111                         return emptyWebResourceResponse;
6112                     } else if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched Fanboy's Social Blocking List's whitelist.
6113                         // Update the whitelist result string array tracker.
6114                         whitelistResultStringArray = new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
6115                                 fanboysSocialListResults[4], fanboysSocialListResults[5]};
6116                     }
6117                 }
6118
6119                 // Add the request to the log because it hasn't been processed by any of the previous checks.
6120                 if (whitelistResultStringArray != null) {  // The request was processed by a whitelist.
6121                     nestedScrollWebView.addResourceRequest(whitelistResultStringArray);
6122                 } else {  // The request didn't match any blocklist entry.  Log it as a default request.
6123                     nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_DEFAULT, url});
6124                 }
6125
6126                 // The resource request has not been blocked.  `return null` loads the requested resource.
6127                 return null;
6128             }
6129
6130             // Handle HTTP authentication requests.
6131             @Override
6132             public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
6133                 // Store the handler.
6134                 nestedScrollWebView.setHttpAuthHandler(handler);
6135
6136                 // Instantiate an HTTP authentication dialog.
6137                 DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.getWebViewFragmentId());
6138
6139                 // Show the HTTP authentication dialog.
6140                 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
6141             }
6142
6143             @Override
6144             public void onPageStarted(WebView view, String url, Bitmap favicon) {
6145                 // Set the padding and layout settings if the app bar is at the top of the screen.
6146                 if (!bottomAppBar) {
6147                     // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.  This can't be done in `appAppSettings()` because the app bar is not yet populated there.
6148                     if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
6149                         // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
6150                         swipeRefreshLayout.setPadding(0, 0, 0, 0);
6151
6152                         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
6153                         swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
6154                     } else {
6155                         // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
6156                         appBarHeight = appBarLayout.getHeight();
6157
6158                         // The swipe refresh layout must be manually moved below the app bar layout.
6159                         swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
6160
6161                         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
6162                         swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
6163                     }
6164                 }
6165
6166                 // Reset the list of resource requests.
6167                 nestedScrollWebView.clearResourceRequests();
6168
6169                 // Reset the requests counters.
6170                 nestedScrollWebView.resetRequestsCounters();
6171
6172                 // Get the current page position.
6173                 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
6174
6175                 // Update the URL text bar if the page is currently selected and the URL edit text is not currently being edited.
6176                 if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus()) {
6177                     // Display the formatted URL text.
6178                     urlEditText.setText(url);
6179
6180                     // Apply text highlighting to the URL text box.
6181                     highlightUrlText();
6182
6183                     // Hide the keyboard.
6184                     inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
6185                 }
6186
6187                 // Reset the list of host IP addresses.
6188                 nestedScrollWebView.clearCurrentIpAddresses();
6189
6190                 // Get a URI for the current URL.
6191                 Uri currentUri = Uri.parse(url);
6192
6193                 // Get the IP addresses for the host.
6194                 new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
6195
6196                 // Replace Refresh with Stop if the options menu has been created.  (The first WebView typically begins loading before the menu items are instantiated.)
6197                 if (optionsMenu != null) {
6198                     // Set the title.
6199                     optionsRefreshMenuItem.setTitle(R.string.stop);
6200
6201                     // Get the app bar and theme preferences.
6202                     boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
6203
6204                     // If the icon is displayed in the AppBar, set it according to the theme.
6205                     if (displayAdditionalAppBarIcons) {
6206                         // Get the current theme status.
6207                         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
6208
6209                         // Set the stop icon according to the theme.
6210                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
6211                             optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
6212                         } else {
6213                             optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
6214                         }
6215                     }
6216                 }
6217             }
6218
6219             @Override
6220             public void onPageFinished(WebView view, String url) {
6221                 // Flush any cookies to persistent storage.  The cookie manager has become very lazy about flushing cookies in recent versions.
6222                 if (nestedScrollWebView.getAcceptCookies() && Build.VERSION.SDK_INT >= 21) {
6223                     CookieManager.getInstance().flush();
6224                 }
6225
6226                 // Update the Refresh menu item if the options menu has been created.
6227                 if (optionsMenu != null) {
6228                     // Reset the Refresh title.
6229                     optionsRefreshMenuItem.setTitle(R.string.refresh);
6230
6231                     // Get the app bar and theme preferences.
6232                     boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
6233
6234                     // If the icon is displayed in the app bar, reset it according to the theme.
6235                     if (displayAdditionalAppBarIcons) {
6236                         // Get the current theme status.
6237                         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
6238
6239                         // Set the icon according to the theme.
6240                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
6241                             optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_day);
6242                         } else {
6243                             optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled_night);
6244                         }
6245                     }
6246                 }
6247
6248                 // Clear the cache, history, and logcat if Incognito Mode is enabled.
6249                 if (incognitoModeEnabled) {
6250                     // Clear the cache.  `true` includes disk files.
6251                     nestedScrollWebView.clearCache(true);
6252
6253                     // Clear the back/forward history.
6254                     nestedScrollWebView.clearHistory();
6255
6256                     // Manually delete cache folders.
6257                     try {
6258                         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
6259                         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
6260                         String privateDataDirectoryString = getApplicationInfo().dataDir;
6261
6262                         // Delete the main cache directory.
6263                         Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache");
6264
6265                         // Delete the secondary `Service Worker` cache directory.
6266                         // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
6267                         Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
6268                     } catch (IOException exception) {
6269                         // Do nothing if an error is thrown.
6270                     }
6271
6272                     // Clear the logcat.
6273                     try {
6274                         // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
6275                         Runtime.getRuntime().exec("logcat -b all -c");
6276                     } catch (IOException exception) {
6277                         // Do nothing.
6278                     }
6279                 }
6280
6281                 // Get the current page position.
6282                 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
6283
6284                 // Get the current URL from the nested scroll WebView.  This is more accurate than using the URL passed into the method, which is sometimes not the final one.
6285                 String currentUrl = nestedScrollWebView.getUrl();
6286
6287                 // Get the current tab.
6288                 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
6289
6290                 // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
6291                 // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
6292                 // Probably some sort of race condition when Privacy Browser is being resumed.
6293                 if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) {
6294                     // Check to see if the URL is `about:blank`.
6295                     if (currentUrl.equals("about:blank")) {  // The WebView is blank.
6296                         // Display the hint in the URL edit text.
6297                         urlEditText.setText("");
6298
6299                         // Request focus for the URL text box.
6300                         urlEditText.requestFocus();
6301
6302                         // Display the keyboard.
6303                         inputMethodManager.showSoftInput(urlEditText, 0);
6304
6305                         // Apply the domain settings.  This clears any settings from the previous domain.
6306                         applyDomainSettings(nestedScrollWebView, "", true, false, false);
6307
6308                         // Only populate the title text view if the tab has been fully created.
6309                         if (tab != null) {
6310                             // Get the custom view from the tab.
6311                             View tabView = tab.getCustomView();
6312
6313                             // Remove the incorrect warning below that the current tab view might be null.
6314                             assert tabView != null;
6315
6316                             // Get the title text view from the tab.
6317                             TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
6318
6319                             // Set the title as the tab text.
6320                             tabTitleTextView.setText(R.string.new_tab);
6321                         }
6322                     } else {  // The WebView has loaded a webpage.
6323                         // Update the URL edit text if it is not currently being edited.
6324                         if (!urlEditText.hasFocus()) {
6325                             // Sanitize the current URL.  This removes unwanted URL elements that were added by redirects, so that they won't be included if the URL is shared.
6326                             String sanitizedUrl = sanitizeUrl(currentUrl);
6327
6328                             // Display the final URL.  Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
6329                             urlEditText.setText(sanitizedUrl);
6330
6331                             // Apply text highlighting to the URL.
6332                             highlightUrlText();
6333                         }
6334
6335                         // Only populate the title text view if the tab has been fully created.
6336                         if (tab != null) {
6337                             // Get the custom view from the tab.
6338                             View tabView = tab.getCustomView();
6339
6340                             // Remove the incorrect warning below that the current tab view might be null.
6341                             assert tabView != null;
6342
6343                             // Get the title text view from the tab.
6344                             TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
6345
6346                             // Set the title as the tab text.  Sometimes `onReceivedTitle()` is not called, especially when navigating history.
6347                             tabTitleTextView.setText(nestedScrollWebView.getTitle());
6348                         }
6349                     }
6350                 }
6351             }
6352
6353             // Handle SSL Certificate errors.
6354             @Override
6355             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
6356                 // Get the current website SSL certificate.
6357                 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
6358
6359                 // Extract the individual pieces of information from the current website SSL certificate.
6360                 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
6361                 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
6362                 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
6363                 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
6364                 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
6365                 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
6366                 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
6367                 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
6368
6369                 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
6370                 if (nestedScrollWebView.hasPinnedSslCertificate()) {
6371                     // Get the pinned SSL certificate.
6372                     ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
6373
6374                     // Extract the arrays from the array list.
6375                     String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
6376                     Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
6377
6378                     // Check if the current SSL certificate matches the pinned certificate.
6379                     if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&
6380                         currentWebsiteIssuedToUName.equals(pinnedSslCertificateStringArray[2]) && currentWebsiteIssuedByCName.equals(pinnedSslCertificateStringArray[3]) &&
6381                         currentWebsiteIssuedByOName.equals(pinnedSslCertificateStringArray[4]) && currentWebsiteIssuedByUName.equals(pinnedSslCertificateStringArray[5]) &&
6382                         currentWebsiteSslStartDate.equals(pinnedSslCertificateDateArray[0]) && currentWebsiteSslEndDate.equals(pinnedSslCertificateDateArray[1])) {
6383
6384                         // An SSL certificate is pinned and matches the current domain certificate.  Proceed to the website without displaying an error.
6385                         handler.proceed();
6386                     }
6387                 } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
6388                     // Store the SSL error handler.
6389                     nestedScrollWebView.setSslErrorHandler(handler);
6390
6391                     // Instantiate an SSL certificate error alert dialog.
6392                     DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
6393
6394                     // Try to show the dialog.  The SSL error handler continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
6395                     try {
6396                         // Show the SSL certificate error dialog.
6397                         sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
6398                     } catch (Exception exception) {
6399                         // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
6400                         pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
6401                     }
6402                 }
6403             }
6404         });
6405
6406         // Check to see if the state is being restored.
6407         if (restoringState) {  // The state is being restored.
6408             // Resume the nested scroll WebView JavaScript timers.
6409             nestedScrollWebView.resumeTimers();
6410         } else if (pageNumber == 0) {  // The first page is being loaded.
6411             // Set this nested scroll WebView as the current WebView.
6412             currentWebView = nestedScrollWebView;
6413
6414             // Initialize the URL to load string.
6415             String urlToLoadString;
6416
6417             // Get the intent that started the app.
6418             Intent launchingIntent = getIntent();
6419
6420             // Reset the intent.  This prevents a duplicate tab from being created on restart.
6421             setIntent(new Intent());
6422
6423             // Get the information from the intent.
6424             String launchingIntentAction = launchingIntent.getAction();
6425             Uri launchingIntentUriData = launchingIntent.getData();
6426             String launchingIntentStringExtra = launchingIntent.getStringExtra(Intent.EXTRA_TEXT);
6427
6428             // Parse the launching intent URL.
6429             if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {  // The intent contains a search string.
6430                 // Create an encoded URL string.
6431                 String encodedUrlString;
6432
6433                 // Sanitize the search input and convert it to a search.
6434                 try {
6435                     encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
6436                 } catch (UnsupportedEncodingException exception) {
6437                     encodedUrlString = "";
6438                 }
6439
6440                 // Store the web search as the URL to load.
6441                 urlToLoadString = searchURL + encodedUrlString;
6442             } else if (launchingIntentUriData != null) {  // The launching intent contains a URL formatted as a URI.
6443                 // Store the URI as a URL.
6444                 urlToLoadString = launchingIntentUriData.toString();
6445             } else if (launchingIntentStringExtra != null) {  // The launching intent contains text that might be a URL.
6446                 // Store the URL.
6447                 urlToLoadString = launchingIntentStringExtra;
6448             } else if (!url.equals("")) {  // The activity has been restarted.
6449                 // Load the saved URL.
6450                 urlToLoadString = url;
6451             } else {  // The is no URL in the intent.
6452                 // Store the homepage to be loaded.
6453                 urlToLoadString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
6454             }
6455
6456             // Load the website if not waiting for the proxy.
6457             if (waitingForProxy) {  // Store the URL to be loaded in the Nested Scroll WebView.
6458                 nestedScrollWebView.setWaitingForProxyUrlString(urlToLoadString);
6459             } else {  // Load the URL.
6460                 loadUrl(nestedScrollWebView, urlToLoadString);
6461             }
6462
6463             // Reset the intent.  This prevents a duplicate tab from being created on a subsequent restart if loading an link from a new intent on restart.
6464             // For example, this prevents a duplicate tab if a link is loaded from the Guide after changing the theme in the guide and then changing the theme again in the main activity.
6465             setIntent(new Intent());
6466         } else {  // This is not the first tab.
6467             // Load the URL.
6468             loadUrl(nestedScrollWebView, url);
6469
6470             // Set the focus and display the keyboard if the URL is blank.
6471             if (url.equals("")) {
6472                 // Request focus for the URL text box.
6473                 urlEditText.requestFocus();
6474
6475                 // Create a display keyboard handler.
6476                 Handler displayKeyboardHandler = new Handler();
6477
6478                 // Create a display keyboard runnable.
6479                 Runnable displayKeyboardRunnable = () -> {
6480                     // Display the keyboard.
6481                     inputMethodManager.showSoftInput(urlEditText, 0);
6482                 };
6483
6484                 // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition.
6485                 displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100);
6486             }
6487         }
6488     }
6489 }