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