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