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