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