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