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