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