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