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