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