509fe8f7d78d5e398a317ed342bd566b1b9029e3
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
1 /*
2  * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
3  *
4  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
5  *
6  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
7  *
8  * Privacy Browser is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * Privacy Browser is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 package com.stoutner.privacybrowser.activities;
23
24 import android.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.DownloadManager;
28 import android.app.SearchManager;
29 import android.content.ActivityNotFoundException;
30 import android.content.BroadcastReceiver;
31 import android.content.ClipData;
32 import android.content.ClipboardManager;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.SharedPreferences;
37 import android.content.pm.PackageManager;
38 import android.content.res.Configuration;
39 import android.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.BitmapFactory;
42 import android.graphics.Typeface;
43 import android.graphics.drawable.BitmapDrawable;
44 import android.graphics.drawable.Drawable;
45 import android.net.Uri;
46 import android.net.http.SslCertificate;
47 import android.net.http.SslError;
48 import android.os.Build;
49 import android.os.Bundle;
50 import android.os.Environment;
51 import android.os.Handler;
52 import android.preference.PreferenceManager;
53 import android.print.PrintDocumentAdapter;
54 import android.print.PrintManager;
55 import android.text.Editable;
56 import android.text.Spanned;
57 import android.text.TextWatcher;
58 import android.text.style.ForegroundColorSpan;
59 import android.util.Patterns;
60 import android.view.ContextMenu;
61 import android.view.GestureDetector;
62 import android.view.KeyEvent;
63 import android.view.Menu;
64 import android.view.MenuItem;
65 import android.view.MotionEvent;
66 import android.view.View;
67 import android.view.ViewGroup;
68 import android.view.WindowManager;
69 import android.view.inputmethod.InputMethodManager;
70 import android.webkit.CookieManager;
71 import android.webkit.HttpAuthHandler;
72 import android.webkit.SslErrorHandler;
73 import android.webkit.ValueCallback;
74 import android.webkit.WebChromeClient;
75 import android.webkit.WebResourceResponse;
76 import android.webkit.WebSettings;
77 import android.webkit.WebStorage;
78 import android.webkit.WebView;
79 import android.webkit.WebViewClient;
80 import android.webkit.WebViewDatabase;
81 import android.widget.ArrayAdapter;
82 import android.widget.CursorAdapter;
83 import android.widget.EditText;
84 import android.widget.FrameLayout;
85 import android.widget.ImageView;
86 import android.widget.LinearLayout;
87 import android.widget.ListView;
88 import android.widget.ProgressBar;
89 import android.widget.RadioButton;
90 import android.widget.RelativeLayout;
91 import android.widget.TextView;
92
93 import androidx.annotation.NonNull;
94 import androidx.appcompat.app.ActionBar;
95 import androidx.appcompat.app.ActionBarDrawerToggle;
96 import androidx.appcompat.app.AppCompatActivity;
97 import androidx.appcompat.widget.Toolbar;
98 import androidx.coordinatorlayout.widget.CoordinatorLayout;
99 import androidx.core.app.ActivityCompat;
100 import androidx.core.content.ContextCompat;
101 import androidx.core.view.GravityCompat;
102 import androidx.drawerlayout.widget.DrawerLayout;
103 import androidx.fragment.app.DialogFragment;
104 import androidx.fragment.app.FragmentManager;
105 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
106 import androidx.viewpager.widget.ViewPager;
107
108 import com.google.android.material.appbar.AppBarLayout;
109 import com.google.android.material.floatingactionbutton.FloatingActionButton;
110 import com.google.android.material.navigation.NavigationView;
111 import com.google.android.material.snackbar.Snackbar;
112 import com.google.android.material.tabs.TabLayout;
113
114 import com.stoutner.privacybrowser.BuildConfig;
115 import com.stoutner.privacybrowser.R;
116 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
117 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
118 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
119 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
120 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
121 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
122 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
123 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
124 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
125 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
126 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
127 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
128 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
129 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
130 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
131 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
132 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
133 import com.stoutner.privacybrowser.helpers.AdHelper;
134 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
135 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
136 import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
137 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
138 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
139 import com.stoutner.privacybrowser.views.NestedScrollWebView;
140
141 import java.io.ByteArrayInputStream;
142 import java.io.ByteArrayOutputStream;
143 import java.io.File;
144 import java.io.IOException;
145 import java.io.UnsupportedEncodingException;
146 import java.net.MalformedURLException;
147 import java.net.URL;
148 import java.net.URLDecoder;
149 import java.net.URLEncoder;
150 import java.util.ArrayList;
151 import java.util.Date;
152 import java.util.HashMap;
153 import java.util.HashSet;
154 import java.util.List;
155 import java.util.Map;
156 import java.util.Set;
157
158 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
159 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
160         DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
161         EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, PopulateBlocklists.PopulateBlocklistsListener, WebViewTabFragment.NewTabListener {
162
163     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
164     public static String orbotStatus;
165
166     // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`.  It is also used in `onCreate()`, `onResume()`, and `addTab()`.
167     public static WebViewPagerAdapter webViewPagerAdapter;
168
169     // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`.  They are used in `onRestart()`.
170     public static boolean loadUrlOnRestart;
171     public static String urlToLoadOnRestart;
172
173     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
174     public static boolean restartFromBookmarksActivity;
175
176     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
177     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
178     public static String currentBookmarksFolder;
179
180     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
181     public final static int UNRECOGNIZED_USER_AGENT = -1;
182     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
183     public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
184     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
185     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
186     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
187
188
189
190     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
191     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`.
192     private NestedScrollWebView currentWebView;
193
194     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
195     private final Map<String, String> customHeaders = new HashMap<>();
196
197     // The search URL is set in `applyProxyThroughOrbot()` and used in `onCreate()`, `onNewIntent()`, `loadURLFromTextBox()`, and `initializeWebView()`.
198     private String searchURL;
199
200     // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
201     private Menu optionsMenu;
202
203     // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
204     private ArrayList<List<String[]>> easyList;
205     private ArrayList<List<String[]>> easyPrivacy;
206     private ArrayList<List<String[]>> fanboysAnnoyanceList;
207     private ArrayList<List<String[]>> fanboysSocialList;
208     private ArrayList<List<String[]>> ultraPrivacy;
209
210     // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
211     private String webViewDefaultUserAgent;
212
213     // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
214     private boolean proxyThroughOrbot;
215
216     // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
217     private boolean incognitoModeEnabled;
218
219     // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
220     private boolean fullScreenBrowsingModeEnabled;
221
222     // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
223     private boolean inFullScreenBrowsingMode;
224
225     // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`.
226     private boolean hideAppBar;
227     private boolean scrollAppBar;
228
229     // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
230     private boolean loadingNewIntent;
231
232     // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
233     private boolean reapplyDomainSettingsOnRestart;
234
235     // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
236     private boolean reapplyAppSettingsOnRestart;
237
238     // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
239     private boolean displayingFullScreenVideo;
240
241     // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
242     private BroadcastReceiver orbotStatusBroadcastReceiver;
243
244     // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
245     private boolean waitingForOrbot;
246
247     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
248     private ActionBarDrawerToggle actionBarDrawerToggle;
249
250     // The color spans are used in `onCreate()` and `highlightUrlText()`.
251     private ForegroundColorSpan redColorSpan;
252     private ForegroundColorSpan initialGrayColorSpan;
253     private ForegroundColorSpan finalGrayColorSpan;
254
255     // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
256     private int drawerHeaderPaddingLeftAndRight;
257     private int drawerHeaderPaddingTop;
258     private int drawerHeaderPaddingBottom;
259
260     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
261     // and `loadBookmarksFolder()`.
262     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
263
264     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
265     private Cursor bookmarksCursor;
266
267     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
268     private CursorAdapter bookmarksCursorAdapter;
269
270     // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
271     private String oldFolderNameString;
272
273     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
274     private ValueCallback<Uri[]> fileChooserCallback;
275
276     // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
277     private int defaultProgressViewStartOffset;
278     private int defaultProgressViewEndOffset;
279
280     // The swipe refresh layout top padding is used when exiting full screen browsing mode.  It is used in an inner class in `initializeWebView()`.
281     private int swipeRefreshLayoutPaddingTop;
282
283     // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
284     private boolean sanitizeGoogleAnalytics;
285     private boolean sanitizeFacebookClickIds;
286     private boolean sanitizeTwitterAmpRedirects;
287
288     // The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
289     private String downloadUrl;
290     private String downloadContentDisposition;
291     private long downloadContentLength;
292
293     // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
294     private String downloadImageUrl;
295
296     // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`.
297     private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
298     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
299
300     @Override
301     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
302     @SuppressLint("ClickableViewAccessibility")
303     protected void onCreate(Bundle savedInstanceState) {
304         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
305         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
306
307         // Get a handle for the shared preferences.
308         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
309
310         // Get the theme and screenshot preferences.
311         boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
312         boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
313
314         // Disable screenshots if not allowed.
315         if (!allowScreenshots) {
316             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
317         }
318
319         // Set the activity theme.
320         if (darkTheme) {
321             setTheme(R.style.PrivacyBrowserDark);
322         } else {
323             setTheme(R.style.PrivacyBrowserLight);
324         }
325
326         // Run the default commands.
327         super.onCreate(savedInstanceState);
328
329         // Set the content view.
330         setContentView(R.layout.main_framelayout);
331
332         // Get handles for the views that need to be modified.
333         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
334         Toolbar toolbar = findViewById(R.id.toolbar);
335         ViewPager webViewPager = findViewById(R.id.webviewpager);
336
337         // Set the action bar.  `SupportActionBar` must be used until the minimum API is >= 21.
338         setSupportActionBar(toolbar);
339
340         // Get a handle for the action bar.
341         ActionBar actionBar = getSupportActionBar();
342
343         // This is needed to get rid of the Android Studio warning that the action bar might be null.
344         assert actionBar != null;
345
346         // Add the custom layout, which shows the URL text bar.
347         actionBar.setCustomView(R.layout.url_app_bar);
348         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
349
350         // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
351         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
352
353         // Create the hamburger icon at the start of the AppBar.
354         actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
355
356         // Initialize the web view pager adapter.
357         webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
358
359         // Set the pager adapter on the web view pager.
360         webViewPager.setAdapter(webViewPagerAdapter);
361
362         // Store up to 100 tabs in memory.
363         webViewPager.setOffscreenPageLimit(100);
364
365         // Populate the blocklists.
366         new PopulateBlocklists(this, this).execute();
367     }
368
369     @Override
370     protected void onNewIntent(Intent intent) {
371         // Replace the intent that started the app with this one.
372         setIntent(intent);
373
374         // 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()`.
375         if (ultraPrivacy != null) {
376             // Get the information from the intent.
377             String intentAction = intent.getAction();
378             Uri intentUriData = intent.getData();
379
380             // Determine if this is a web search.
381             boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
382
383             // 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.
384             if (intentUriData != null || isWebSearch) {
385                 // Get the shared preferences.
386                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
387
388                 // Create a URL string.
389                 String url;
390
391                 // If the intent action is a web search, perform the search.
392                 if (isWebSearch) {
393                     // Create an encoded URL string.
394                     String encodedUrlString;
395
396                     // Sanitize the search input and convert it to a search.
397                     try {
398                         encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
399                     } catch (UnsupportedEncodingException exception) {
400                         encodedUrlString = "";
401                     }
402
403                     // Add the base search URL.
404                     url = searchURL + encodedUrlString;
405                 } else {  // The intent should contain a URL.
406                     // Set the intent data as the URL.
407                     url = intentUriData.toString();
408                 }
409
410                 // Add a new tab if specified in the preferences.
411                 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
412                     // Set the loading new intent flag.
413                     loadingNewIntent = true;
414
415                     // Add a new tab.
416                     addNewTab(url);
417                 } else {  // Load the URL in the current tab.
418                     // Make it so.
419                     loadUrl(url);
420                 }
421
422                 // Get a handle for the drawer layout.
423                 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
424
425                 // Close the navigation drawer if it is open.
426                 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
427                     drawerLayout.closeDrawer(GravityCompat.START);
428                 }
429
430                 // Close the bookmarks drawer if it is open.
431                 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
432                     drawerLayout.closeDrawer(GravityCompat.END);
433                 }
434             }
435         }
436     }
437
438     @Override
439     public void onRestart() {
440         // Run the default commands.
441         super.onRestart();
442
443         // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
444         if (proxyThroughOrbot) {
445             // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
446             Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
447
448             // Send the intent to the Orbot package.
449             orbotIntent.setPackage("org.torproject.android");
450
451             // Make it so.
452             sendBroadcast(orbotIntent);
453         }
454
455         // Apply the app settings if returning from the Settings activity.
456         if (reapplyAppSettingsOnRestart) {
457             // Reset the reapply app settings on restart tracker.
458             reapplyAppSettingsOnRestart = false;
459
460             // Apply the app settings.
461             applyAppSettings();
462         }
463
464         // Apply the domain settings if returning from the settings or domains activity.
465         if (reapplyDomainSettingsOnRestart) {
466             // Reset the reapply domain settings on restart tracker.
467             reapplyDomainSettingsOnRestart = false;
468
469             // Reapply the domain settings for each tab.
470             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
471                 // Get the WebView tab fragment.
472                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
473
474                 // Get the fragment view.
475                 View fragmentView = webViewTabFragment.getView();
476
477                 // Only reload the WebViews if they exist.
478                 if (fragmentView != null) {
479                     // Get the nested scroll WebView from the tab fragment.
480                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
481
482                     // Reset the current domain name so the domain settings will be reapplied.
483                     nestedScrollWebView.resetCurrentDomainName();
484
485                     // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
486                     if (nestedScrollWebView.getUrl() != null) {
487                         applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
488                     }
489                 }
490             }
491         }
492
493         // Load the URL on restart (used when loading a bookmark).
494         if (loadUrlOnRestart) {
495             // Load the specified URL.
496             loadUrl(urlToLoadOnRestart);
497
498             // Reset the load on restart tracker.
499             loadUrlOnRestart = false;
500         }
501
502         // Update the bookmarks drawer if returning from the Bookmarks activity.
503         if (restartFromBookmarksActivity) {
504             // Get a handle for the drawer layout.
505             DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
506
507             // Close the bookmarks drawer.
508             drawerLayout.closeDrawer(GravityCompat.END);
509
510             // Reload the bookmarks drawer.
511             loadBookmarksFolder();
512
513             // Reset `restartFromBookmarksActivity`.
514             restartFromBookmarksActivity = false;
515         }
516
517         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
518         updatePrivacyIcons(true);
519     }
520
521     // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
522     @Override
523     public void onResume() {
524         // Run the default commands.
525         super.onResume();
526
527         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
528             // Get the WebView tab fragment.
529             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
530
531             // Get the fragment view.
532             View fragmentView = webViewTabFragment.getView();
533
534             // Only resume the WebViews if they exist (they won't when the app is first created).
535             if (fragmentView != null) {
536                 // Get the nested scroll WebView from the tab fragment.
537                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
538
539                 // Resume the nested scroll WebView JavaScript timers.
540                 nestedScrollWebView.resumeTimers();
541
542                 // Resume the nested scroll WebView.
543                 nestedScrollWebView.onResume();
544             }
545         }
546
547         // Display a message to the user if waiting for Orbot.
548         if (waitingForOrbot && !orbotStatus.equals("ON")) {
549             // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
550             currentWebView.getSettings().setUseWideViewPort(false);
551
552             // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
553             currentWebView.loadData("<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>", "text/html", null);
554         }
555
556         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
557             // Get a handle for the root frame layouts.
558             FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
559
560             // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
561             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
562
563             /* Hide the system bars.
564              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
565              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
566              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
567              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
568              */
569             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
570                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
571         } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // Resume the adView for the free flavor.
572             // Resume the ad.
573             AdHelper.resumeAd(findViewById(R.id.adview));
574         }
575     }
576
577     @Override
578     public void onPause() {
579         // Run the default commands.
580         super.onPause();
581
582         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
583             // Get the WebView tab fragment.
584             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
585
586             // Get the fragment view.
587             View fragmentView = webViewTabFragment.getView();
588
589             // Only pause the WebViews if they exist (they won't when the app is first created).
590             if (fragmentView != null) {
591                 // Get the nested scroll WebView from the tab fragment.
592                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
593
594                 // Pause the nested scroll WebView.
595                 nestedScrollWebView.onPause();
596
597                 // Pause the nested scroll WebView JavaScript timers.
598                 nestedScrollWebView.pauseTimers();
599             }
600         }
601
602         // Pause the ad or it will continue to consume resources in the background on the free flavor.
603         if (BuildConfig.FLAVOR.contentEquals("free")) {
604             // Pause the ad.
605             AdHelper.pauseAd(findViewById(R.id.adview));
606         }
607     }
608
609     @Override
610     public void onDestroy() {
611         // Unregister the Orbot status broadcast receiver.
612         this.unregisterReceiver(orbotStatusBroadcastReceiver);
613
614         // Close the bookmarks cursor and database.
615         bookmarksCursor.close();
616         bookmarksDatabaseHelper.close();
617
618         // Run the default commands.
619         super.onDestroy();
620     }
621
622     @Override
623     public boolean onCreateOptionsMenu(Menu menu) {
624         // Inflate the menu.  This adds items to the action bar if it is present.
625         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
626
627         // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
628         optionsMenu = menu;
629
630         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
631         updatePrivacyIcons(false);
632
633         // Get handles for the menu items.
634         MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
635         MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
636         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
637         MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
638         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
639         MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
640         MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
641
642         // Only display third-party cookies if API >= 21
643         toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
644
645         // Only display the form data menu items if the API < 26.
646         toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
647         clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
648
649         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
650         clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
651
652         // Only show Ad Consent if this is the free flavor.
653         adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
654
655         // Get the shared preferences.
656         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
657
658         // Get the dark theme and app bar preferences..
659         boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
660         boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
661
662         // 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.
663         if (displayAdditionalAppBarIcons) {
664             toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
665             toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
666             refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
667         } else { //Do not display the additional icons.
668             toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
669             toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
670             refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
671         }
672
673         // Replace Refresh with Stop if a URL is already loading.
674         if (currentWebView != null && currentWebView.getProgress() != 100) {
675             // Set the title.
676             refreshMenuItem.setTitle(R.string.stop);
677
678             // If the icon is displayed in the AppBar, set it according to the theme.
679             if (displayAdditionalAppBarIcons) {
680                 if (darkTheme) {
681                     refreshMenuItem.setIcon(R.drawable.close_dark);
682                 } else {
683                     refreshMenuItem.setIcon(R.drawable.close_light);
684                 }
685             }
686         }
687
688         // Done.
689         return true;
690     }
691
692     @Override
693     public boolean onPrepareOptionsMenu(Menu menu) {
694         // Get handles for the menu items.
695         MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
696         MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
697         MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
698         MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
699         MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
700         MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
701         MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
702         MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
703         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
704         MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
705         MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
706         MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
707         MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
708         MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
709         MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
710         MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
711         MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
712         MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
713         MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
714         MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
715         MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
716         MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
717
718         // Get a handle for the cookie manager.
719         CookieManager cookieManager = CookieManager.getInstance();
720
721         // Initialize the current user agent string and the font size.
722         String currentUserAgent = getString(R.string.user_agent_privacy_browser);
723         int fontSize = 100;
724
725         // 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.
726         if (currentWebView != null) {
727             // Set the add or edit domain text.
728             if (currentWebView.getDomainSettingsApplied()) {
729                 addOrEditDomain.setTitle(R.string.edit_domain_settings);
730             } else {
731                 addOrEditDomain.setTitle(R.string.add_domain_settings);
732             }
733
734             // Get the current user agent from the WebView.
735             currentUserAgent = currentWebView.getSettings().getUserAgentString();
736
737             // Get the current font size from the
738             fontSize = currentWebView.getSettings().getTextZoom();
739
740             // Set the status of the menu item checkboxes.
741             domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
742             saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
743             easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
744             easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
745             fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
746             fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
747             ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
748             blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
749             swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
750             wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
751             displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
752             nightModeMenuItem.setChecked(currentWebView.getNightMode());
753
754             // Initialize the display names for the blocklists with the number of blocked requests.
755             blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
756             easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
757             easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
758             fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
759             fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
760             ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
761             blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
762
763             // Only modify third-party cookies if the API >= 21.
764             if (Build.VERSION.SDK_INT >= 21) {
765                 // Set the status of the third-party cookies checkbox.
766                 thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
767
768                 // Enable third-party cookies if first-party cookies are enabled.
769                 thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
770             }
771
772             // Enable DOM Storage if JavaScript is enabled.
773             domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
774         }
775
776         // Set the status of the menu item checkboxes.
777         firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
778         proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
779
780         // Enable Clear Cookies if there are any.
781         clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
782
783         // 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`.
784         String privateDataDirectoryString = getApplicationInfo().dataDir;
785
786         // Get a count of the number of files in the Local Storage directory.
787         File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
788         int localStorageDirectoryNumberOfFiles = 0;
789         if (localStorageDirectory.exists()) {
790             localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
791         }
792
793         // Get a count of the number of files in the IndexedDB directory.
794         File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
795         int indexedDBDirectoryNumberOfFiles = 0;
796         if (indexedDBDirectory.exists()) {
797             indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
798         }
799
800         // Enable Clear DOM Storage if there is any.
801         clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
802
803         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
804         if (Build.VERSION.SDK_INT < 26) {
805             // Get the WebView database.
806             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
807
808             // Enable the clear form data menu item if there is anything to clear.
809             clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
810         }
811
812         // Enable Clear Data if any of the submenu items are enabled.
813         clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
814
815         // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
816         fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
817
818         // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
819         if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
820             menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
821         } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
822             menu.findItem(R.id.user_agent_webview_default).setChecked(true);
823         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
824             menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
825         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
826             menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
827         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
828             menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
829         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
830             menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
831         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
832             menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
833         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
834             menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
835         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
836             menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
837         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
838             menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
839         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
840             menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
841         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
842             menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
843         } else {  // Custom user agent.
844             menu.findItem(R.id.user_agent_custom).setChecked(true);
845         }
846
847         // Instantiate the font size title and the selected font size menu item.
848         String fontSizeTitle;
849         MenuItem selectedFontSizeMenuItem;
850
851         // Prepare the font size title and current size menu item.
852         switch (fontSize) {
853             case 25:
854                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
855                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
856                 break;
857
858             case 50:
859                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
860                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
861                 break;
862
863             case 75:
864                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
865                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
866                 break;
867
868             case 100:
869                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
870                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
871                 break;
872
873             case 125:
874                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
875                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
876                 break;
877
878             case 150:
879                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
880                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
881                 break;
882
883             case 175:
884                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
885                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
886                 break;
887
888             case 200:
889                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
890                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
891                 break;
892
893             default:
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
899         // Set the font size title and select the current size menu item.
900         fontSizeMenuItem.setTitle(fontSizeTitle);
901         selectedFontSizeMenuItem.setChecked(true);
902
903         // Run all the other default commands.
904         super.onPrepareOptionsMenu(menu);
905
906         // Display the menu.
907         return true;
908     }
909
910     @Override
911     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
912     @SuppressLint("SetJavaScriptEnabled")
913     public boolean onOptionsItemSelected(MenuItem menuItem) {
914         // Reenter full screen browsing mode if it was interrupted by the options menu.  <https://redmine.stoutner.com/issues/389>
915         if (inFullScreenBrowsingMode) {
916             // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
917             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
918
919             FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
920
921             /* Hide the system bars.
922              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
923              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
924              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
925              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
926              */
927             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
928                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
929         }
930
931         // Get the selected menu item ID.
932         int menuItemId = menuItem.getItemId();
933
934         // Get a handle for the shared preferences.
935         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
936
937         // Get a handle for the cookie manager.
938         CookieManager cookieManager = CookieManager.getInstance();
939
940         // Run the commands that correlate to the selected menu item.
941         switch (menuItemId) {
942             case R.id.toggle_javascript:
943                 // Toggle the JavaScript status.
944                 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
945
946                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
947                 updatePrivacyIcons(true);
948
949                 // Display a `Snackbar`.
950                 if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
951                     Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
952                 } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
953                     Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
954                 } else {  // Privacy mode.
955                     Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
956                 }
957
958                 // Reload the current WebView.
959                 currentWebView.reload();
960                 return true;
961
962             case R.id.add_or_edit_domain:
963                 if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
964                     // Reapply the domain settings on returning to `MainWebViewActivity`.
965                     reapplyDomainSettingsOnRestart = true;
966
967                     // Create an intent to launch the domains activity.
968                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
969
970                     // Add the extra information to the intent.
971                     domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
972                     domainsIntent.putExtra("close_on_back", true);
973                     domainsIntent.putExtra("current_url", currentWebView.getUrl());
974
975                     // Get the current certificate.
976                     SslCertificate sslCertificate = currentWebView.getCertificate();
977
978                     // Check to see if the SSL certificate is populated.
979                     if (sslCertificate != null) {
980                         // Extract the certificate to strings.
981                         String issuedToCName = sslCertificate.getIssuedTo().getCName();
982                         String issuedToOName = sslCertificate.getIssuedTo().getOName();
983                         String issuedToUName = sslCertificate.getIssuedTo().getUName();
984                         String issuedByCName = sslCertificate.getIssuedBy().getCName();
985                         String issuedByOName = sslCertificate.getIssuedBy().getOName();
986                         String issuedByUName = sslCertificate.getIssuedBy().getUName();
987                         long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
988                         long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
989
990                         // Add the certificate to the intent.
991                         domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
992                         domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
993                         domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
994                         domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
995                         domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
996                         domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
997                         domainsIntent.putExtra("ssl_start_date", startDateLong);
998                         domainsIntent.putExtra("ssl_end_date", endDateLong);
999                     }
1000
1001                     // Check to see if the current IP addresses have been received.
1002                     if (currentWebView.hasCurrentIpAddresses()) {
1003                         // Add the current IP addresses to the intent.
1004                         domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1005                     }
1006
1007                     // Make it so.
1008                     startActivity(domainsIntent);
1009                 } else {  // Add a new domain.
1010                     // Apply the new domain settings on returning to `MainWebViewActivity`.
1011                     reapplyDomainSettingsOnRestart = true;
1012
1013                     // Get the current domain
1014                     Uri currentUri = Uri.parse(currentWebView.getUrl());
1015                     String currentDomain = currentUri.getHost();
1016
1017                     // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1018                     DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1019
1020                     // Create the domain and store the database ID.
1021                     int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1022
1023                     // Create an intent to launch the domains activity.
1024                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
1025
1026                     // Add the extra information to the intent.
1027                     domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1028                     domainsIntent.putExtra("close_on_back", true);
1029                     domainsIntent.putExtra("current_url", currentWebView.getUrl());
1030
1031                     // Get the current certificate.
1032                     SslCertificate sslCertificate = currentWebView.getCertificate();
1033
1034                     // Check to see if the SSL certificate is populated.
1035                     if (sslCertificate != null) {
1036                         // Extract the certificate to strings.
1037                         String issuedToCName = sslCertificate.getIssuedTo().getCName();
1038                         String issuedToOName = sslCertificate.getIssuedTo().getOName();
1039                         String issuedToUName = sslCertificate.getIssuedTo().getUName();
1040                         String issuedByCName = sslCertificate.getIssuedBy().getCName();
1041                         String issuedByOName = sslCertificate.getIssuedBy().getOName();
1042                         String issuedByUName = sslCertificate.getIssuedBy().getUName();
1043                         long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1044                         long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1045
1046                         // Add the certificate to the intent.
1047                         domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1048                         domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1049                         domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1050                         domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1051                         domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1052                         domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1053                         domainsIntent.putExtra("ssl_start_date", startDateLong);
1054                         domainsIntent.putExtra("ssl_end_date", endDateLong);
1055                     }
1056
1057                     // Check to see if the current IP addresses have been received.
1058                     if (currentWebView.hasCurrentIpAddresses()) {
1059                         // Add the current IP addresses to the intent.
1060                         domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1061                     }
1062
1063                     // Make it so.
1064                     startActivity(domainsIntent);
1065                 }
1066                 return true;
1067
1068             case R.id.toggle_first_party_cookies:
1069                 // Switch the first-party cookie status.
1070                 cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1071
1072                 // Store the first-party cookie status.
1073                 currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
1074
1075                 // Update the menu checkbox.
1076                 menuItem.setChecked(cookieManager.acceptCookie());
1077
1078                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1079                 updatePrivacyIcons(true);
1080
1081                 // Display a snackbar.
1082                 if (cookieManager.acceptCookie()) {  // First-party cookies are enabled.
1083                     Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1084                 } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
1085                     Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1086                 } else {  // Privacy mode.
1087                     Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1088                 }
1089
1090                 // Reload the current WebView.
1091                 currentWebView.reload();
1092                 return true;
1093
1094             case R.id.toggle_third_party_cookies:
1095                 if (Build.VERSION.SDK_INT >= 21) {
1096                     // Switch the status of thirdPartyCookiesEnabled.
1097                     cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
1098
1099                     // Update the menu checkbox.
1100                     menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1101
1102                     // Display a snackbar.
1103                     if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
1104                         Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1105                     } else {
1106                         Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1107                     }
1108
1109                     // Reload the current WebView.
1110                     currentWebView.reload();
1111                 } // Else do nothing because SDK < 21.
1112                 return true;
1113
1114             case R.id.toggle_dom_storage:
1115                 // Toggle the status of domStorageEnabled.
1116                 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1117
1118                 // Update the menu checkbox.
1119                 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1120
1121                 // Update the privacy icon.  `true` refreshes the app bar icons.
1122                 updatePrivacyIcons(true);
1123
1124                 // Display a snackbar.
1125                 if (currentWebView.getSettings().getDomStorageEnabled()) {
1126                     Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1127                 } else {
1128                     Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1129                 }
1130
1131                 // Reload the current WebView.
1132                 currentWebView.reload();
1133                 return true;
1134
1135             // Form data can be removed once the minimum API >= 26.
1136             case R.id.toggle_save_form_data:
1137                 // Switch the status of saveFormDataEnabled.
1138                 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1139
1140                 // Update the menu checkbox.
1141                 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1142
1143                 // Display a snackbar.
1144                 if (currentWebView.getSettings().getSaveFormData()) {
1145                     Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1146                 } else {
1147                     Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1148                 }
1149
1150                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1151                 updatePrivacyIcons(true);
1152
1153                 // Reload the current WebView.
1154                 currentWebView.reload();
1155                 return true;
1156
1157             case R.id.clear_cookies:
1158                 Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1159                         .setAction(R.string.undo, v -> {
1160                             // Do nothing because everything will be handled by `onDismissed()` below.
1161                         })
1162                         .addCallback(new Snackbar.Callback() {
1163                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1164                             @Override
1165                             public void onDismissed(Snackbar snackbar, int event) {
1166                                 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1167                                     // Delete the cookies, which command varies by SDK.
1168                                     if (Build.VERSION.SDK_INT < 21) {
1169                                         cookieManager.removeAllCookie();
1170                                     } else {
1171                                         cookieManager.removeAllCookies(null);
1172                                     }
1173                                 }
1174                             }
1175                         })
1176                         .show();
1177                 return true;
1178
1179             case R.id.clear_dom_storage:
1180                 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1181                         .setAction(R.string.undo, v -> {
1182                             // Do nothing because everything will be handled by `onDismissed()` below.
1183                         })
1184                         .addCallback(new Snackbar.Callback() {
1185                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1186                             @Override
1187                             public void onDismissed(Snackbar snackbar, int event) {
1188                                 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1189                                     // Delete the DOM Storage.
1190                                     WebStorage webStorage = WebStorage.getInstance();
1191                                     webStorage.deleteAllData();
1192
1193                                     // Initialize a handler to manually delete the DOM storage files and directories.
1194                                     Handler deleteDomStorageHandler = new Handler();
1195
1196                                     // Setup a runnable to manually delete the DOM storage files and directories.
1197                                     Runnable deleteDomStorageRunnable = () -> {
1198                                         try {
1199                                             // Get a handle for the runtime.
1200                                             Runtime runtime = Runtime.getRuntime();
1201
1202                                             // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1203                                             // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1204                                             String privateDataDirectoryString = getApplicationInfo().dataDir;
1205
1206                                             // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1207                                             Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1208
1209                                             // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1210                                             Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1211                                             Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1212                                             Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1213                                             Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1214
1215                                             // Wait for the processes to finish.
1216                                             deleteLocalStorageProcess.waitFor();
1217                                             deleteIndexProcess.waitFor();
1218                                             deleteQuotaManagerProcess.waitFor();
1219                                             deleteQuotaManagerJournalProcess.waitFor();
1220                                             deleteDatabasesProcess.waitFor();
1221                                         } catch (Exception exception) {
1222                                             // Do nothing if an error is thrown.
1223                                         }
1224                                     };
1225
1226                                     // Manually delete the DOM storage files after 200 milliseconds.
1227                                     deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1228                                 }
1229                             }
1230                         })
1231                         .show();
1232                 return true;
1233
1234             // Form data can be remove once the minimum API >= 26.
1235             case R.id.clear_form_data:
1236                 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1237                         .setAction(R.string.undo, v -> {
1238                             // Do nothing because everything will be handled by `onDismissed()` below.
1239                         })
1240                         .addCallback(new Snackbar.Callback() {
1241                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1242                             @Override
1243                             public void onDismissed(Snackbar snackbar, int event) {
1244                                 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1245                                     // Delete the form data.
1246                                     WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1247                                     mainWebViewDatabase.clearFormData();
1248                                 }
1249                             }
1250                         })
1251                         .show();
1252                 return true;
1253
1254             case R.id.easylist:
1255                 // Toggle the EasyList status.
1256                 currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1257
1258                 // Update the menu checkbox.
1259                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1260
1261                 // Reload the current WebView.
1262                 currentWebView.reload();
1263                 return true;
1264
1265             case R.id.easyprivacy:
1266                 // Toggle the EasyPrivacy status.
1267                 currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1268
1269                 // Update the menu checkbox.
1270                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1271
1272                 // Reload the current WebView.
1273                 currentWebView.reload();
1274                 return true;
1275
1276             case R.id.fanboys_annoyance_list:
1277                 // Toggle Fanboy's Annoyance List status.
1278                 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1279
1280                 // Update the menu checkbox.
1281                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1282
1283                 // Update the staus of Fanboy's Social Blocking List.
1284                 MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
1285                 fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1286
1287                 // Reload the current WebView.
1288                 currentWebView.reload();
1289                 return true;
1290
1291             case R.id.fanboys_social_blocking_list:
1292                 // Toggle Fanboy's Social Blocking List status.
1293                 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1294
1295                 // Update the menu checkbox.
1296                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1297
1298                 // Reload the current WebView.
1299                 currentWebView.reload();
1300                 return true;
1301
1302             case R.id.ultraprivacy:
1303                 // Toggle the UltraPrivacy status.
1304                 currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1305
1306                 // Update the menu checkbox.
1307                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1308
1309                 // Reload the current WebView.
1310                 currentWebView.reload();
1311                 return true;
1312
1313             case R.id.block_all_third_party_requests:
1314                 //Toggle the third-party requests blocker status.
1315                 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1316
1317                 // Update the menu checkbox.
1318                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1319
1320                 // Reload the current WebView.
1321                 currentWebView.reload();
1322                 return true;
1323
1324             case R.id.user_agent_privacy_browser:
1325                 // Update the user agent.
1326                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1327
1328                 // Reload the current WebView.
1329                 currentWebView.reload();
1330                 return true;
1331
1332             case R.id.user_agent_webview_default:
1333                 // Update the user agent.
1334                 currentWebView.getSettings().setUserAgentString("");
1335
1336                 // Reload the current WebView.
1337                 currentWebView.reload();
1338                 return true;
1339
1340             case R.id.user_agent_firefox_on_android:
1341                 // Update the user agent.
1342                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1343
1344                 // Reload the current WebView.
1345                 currentWebView.reload();
1346                 return true;
1347
1348             case R.id.user_agent_chrome_on_android:
1349                 // Update the user agent.
1350                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1351
1352                 // Reload the current WebView.
1353                 currentWebView.reload();
1354                 return true;
1355
1356             case R.id.user_agent_safari_on_ios:
1357                 // Update the user agent.
1358                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1359
1360                 // Reload the current WebView.
1361                 currentWebView.reload();
1362                 return true;
1363
1364             case R.id.user_agent_firefox_on_linux:
1365                 // Update the user agent.
1366                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1367
1368                 // Reload the current WebView.
1369                 currentWebView.reload();
1370                 return true;
1371
1372             case R.id.user_agent_chromium_on_linux:
1373                 // Update the user agent.
1374                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1375
1376                 // Reload the current WebView.
1377                 currentWebView.reload();
1378                 return true;
1379
1380             case R.id.user_agent_firefox_on_windows:
1381                 // Update the user agent.
1382                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1383
1384                 // Reload the current WebView.
1385                 currentWebView.reload();
1386                 return true;
1387
1388             case R.id.user_agent_chrome_on_windows:
1389                 // Update the user agent.
1390                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1391
1392                 // Reload the current WebView.
1393                 currentWebView.reload();
1394                 return true;
1395
1396             case R.id.user_agent_edge_on_windows:
1397                 // Update the user agent.
1398                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1399
1400                 // Reload the current WebView.
1401                 currentWebView.reload();
1402                 return true;
1403
1404             case R.id.user_agent_internet_explorer_on_windows:
1405                 // Update the user agent.
1406                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1407
1408                 // Reload the current WebView.
1409                 currentWebView.reload();
1410                 return true;
1411
1412             case R.id.user_agent_safari_on_macos:
1413                 // Update the user agent.
1414                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1415
1416                 // Reload the current WebView.
1417                 currentWebView.reload();
1418                 return true;
1419
1420             case R.id.user_agent_custom:
1421                 // Update the user agent.
1422                 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1423
1424                 // Reload the current WebView.
1425                 currentWebView.reload();
1426                 return true;
1427
1428             case R.id.font_size_twenty_five_percent:
1429                 currentWebView.getSettings().setTextZoom(25);
1430                 return true;
1431
1432             case R.id.font_size_fifty_percent:
1433                 currentWebView.getSettings().setTextZoom(50);
1434                 return true;
1435
1436             case R.id.font_size_seventy_five_percent:
1437                 currentWebView.getSettings().setTextZoom(75);
1438                 return true;
1439
1440             case R.id.font_size_one_hundred_percent:
1441                 currentWebView.getSettings().setTextZoom(100);
1442                 return true;
1443
1444             case R.id.font_size_one_hundred_twenty_five_percent:
1445                 currentWebView.getSettings().setTextZoom(125);
1446                 return true;
1447
1448             case R.id.font_size_one_hundred_fifty_percent:
1449                 currentWebView.getSettings().setTextZoom(150);
1450                 return true;
1451
1452             case R.id.font_size_one_hundred_seventy_five_percent:
1453                 currentWebView.getSettings().setTextZoom(175);
1454                 return true;
1455
1456             case R.id.font_size_two_hundred_percent:
1457                 currentWebView.getSettings().setTextZoom(200);
1458                 return true;
1459
1460             case R.id.swipe_to_refresh:
1461                 // Toggle the stored status of swipe to refresh.
1462                 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1463
1464                 // Get a handle for the swipe refresh layout.
1465                 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
1466
1467                 // Update the swipe refresh layout.
1468                 if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
1469                     // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
1470                     swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
1471                 } else {  // Swipe to refresh is disabled.
1472                     // Disable the swipe refresh layout.
1473                     swipeRefreshLayout.setEnabled(false);
1474                 }
1475                 return true;
1476
1477             case R.id.wide_viewport:
1478                 // Toggle the viewport.
1479                 currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1480                 return true;
1481
1482             case R.id.display_images:
1483                 if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
1484                     // Disable loading of images.
1485                     currentWebView.getSettings().setLoadsImagesAutomatically(false);
1486
1487                     // Reload the website to remove existing images.
1488                     currentWebView.reload();
1489                 } else {  // Images are not currently loaded automatically.
1490                     // Enable loading of images.  Missing images will be loaded without the need for a reload.
1491                     currentWebView.getSettings().setLoadsImagesAutomatically(true);
1492                 }
1493                 return true;
1494
1495             case R.id.night_mode:
1496                 // Toggle night mode.
1497                 currentWebView.setNightMode(!currentWebView.getNightMode());
1498
1499                 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
1500                 if (currentWebView.getNightMode()) {  // Night mode is enabled, which requires JavaScript.
1501                     // Enable JavaScript.
1502                     currentWebView.getSettings().setJavaScriptEnabled(true);
1503                 } else if (currentWebView.getDomainSettingsApplied()) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
1504                     // Apply the JavaScript preference that was stored the last time domain settings were loaded.
1505                     currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled());
1506                 } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
1507                     // Apply the JavaScript preference.
1508                     currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
1509                 }
1510
1511                 // Update the privacy icons.
1512                 updatePrivacyIcons(false);
1513
1514                 // Reload the website.
1515                 currentWebView.reload();
1516                 return true;
1517
1518             case R.id.find_on_page:
1519                 // Get a handle for the views.
1520                 Toolbar toolbar = findViewById(R.id.toolbar);
1521                 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1522                 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1523
1524                 // Set the minimum height of the find on page linear layout to match the toolbar.
1525                 findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1526
1527                 // Hide the toolbar.
1528                 toolbar.setVisibility(View.GONE);
1529
1530                 // Show the find on page linear layout.
1531                 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1532
1533                 // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
1534                 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1535                 findOnPageEditText.postDelayed(() -> {
1536                     // Set the focus on `findOnPageEditText`.
1537                     findOnPageEditText.requestFocus();
1538
1539                     // Get a handle for the input method manager.
1540                     InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1541
1542                     // Remove the lint warning below that the input method manager might be null.
1543                     assert inputMethodManager != null;
1544
1545                     // Display the keyboard.  `0` sets no input flags.
1546                     inputMethodManager.showSoftInput(findOnPageEditText, 0);
1547                 }, 200);
1548                 return true;
1549
1550             case R.id.print:
1551                 // Get a print manager instance.
1552                 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1553
1554                 // Remove the lint error below that print manager might be null.
1555                 assert printManager != null;
1556
1557                 // Create a print document adapter from the current WebView.
1558                 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1559
1560                 // Print the document.
1561                 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
1562                 return true;
1563
1564             case R.id.add_to_homescreen:
1565                 // Instantiate the create home screen shortcut dialog.
1566                 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1567                         currentWebView.getFavoriteOrDefaultIcon());
1568
1569                 // Show the create home screen shortcut dialog.
1570                 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1571                 return true;
1572
1573             case R.id.view_source:
1574                 // Create an intent to launch the view source activity.
1575                 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1576
1577                 // Add the variables to the intent.
1578                 viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
1579                 viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
1580
1581                 // Make it so.
1582                 startActivity(viewSourceIntent);
1583                 return true;
1584
1585             case R.id.share_url:
1586                 // Setup the share string.
1587                 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1588
1589                 // Create the share intent.
1590                 Intent shareIntent = new Intent(Intent.ACTION_SEND);
1591                 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1592                 shareIntent.setType("text/plain");
1593
1594                 // Make it so.
1595                 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1596                 return true;
1597
1598             case R.id.open_with_app:
1599                 openWithApp(currentWebView.getUrl());
1600                 return true;
1601
1602             case R.id.open_with_browser:
1603                 openWithBrowser(currentWebView.getUrl());
1604                 return true;
1605
1606             case R.id.proxy_through_orbot:
1607                 // Toggle the proxy through Orbot variable.
1608                 proxyThroughOrbot = !proxyThroughOrbot;
1609
1610                 // Apply the proxy through Orbot settings.
1611                 applyProxyThroughOrbot(true);
1612                 return true;
1613
1614             case R.id.refresh:
1615                 if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
1616                     // Reload the current WebView.
1617                     currentWebView.reload();
1618                 } else {  // The stop button was pushed.
1619                     // Stop the loading of the WebView.
1620                     currentWebView.stopLoading();
1621                 }
1622                 return true;
1623
1624             case R.id.ad_consent:
1625                 // Display the ad consent dialog.
1626                 DialogFragment adConsentDialogFragment = new AdConsentDialog();
1627                 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
1628                 return true;
1629
1630             default:
1631                 // Don't consume the event.
1632                 return super.onOptionsItemSelected(menuItem);
1633         }
1634     }
1635
1636     // removeAllCookies is deprecated, but it is required for API < 21.
1637     @Override
1638     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1639         // Get the menu item ID.
1640         int menuItemId = menuItem.getItemId();
1641
1642         // Get a handle for the shared preferences.
1643         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1644
1645         // Run the commands that correspond to the selected menu item.
1646         switch (menuItemId) {
1647             case R.id.clear_and_exit:
1648                 // Clear and exit Privacy Browser.
1649                 clearAndExit();
1650                 break;
1651
1652             case R.id.home:
1653                 // Select the homepage based on the proxy through Orbot status.
1654                 if (proxyThroughOrbot) {
1655                     // Load the Tor homepage.
1656                     loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
1657                 } else {
1658                     // Load the normal homepage.
1659                     loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
1660                 }
1661                 break;
1662
1663             case R.id.back:
1664                 if (currentWebView.canGoBack()) {
1665                     // Reset the current domain name so that navigation works if third-party requests are blocked.
1666                     currentWebView.resetCurrentDomainName();
1667
1668                     // Set navigating history so that the domain settings are applied when the new URL is loaded.
1669                     currentWebView.setNavigatingHistory(true);
1670
1671                     // Load the previous website in the history.
1672                     currentWebView.goBack();
1673                 }
1674                 break;
1675
1676             case R.id.forward:
1677                 if (currentWebView.canGoForward()) {
1678                     // Reset the current domain name so that navigation works if third-party requests are blocked.
1679                     currentWebView.resetCurrentDomainName();
1680
1681                     // Set navigating history so that the domain settings are applied when the new URL is loaded.
1682                     currentWebView.setNavigatingHistory(true);
1683
1684                     // Load the next website in the history.
1685                     currentWebView.goForward();
1686                 }
1687                 break;
1688
1689             case R.id.history:
1690                 // Instantiate the URL history dialog.
1691                 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
1692
1693                 // Show the URL history dialog.
1694                 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
1695                 break;
1696
1697             case R.id.requests:
1698                 // Populate the resource requests.
1699                 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
1700
1701                 // Create an intent to launch the Requests activity.
1702                 Intent requestsIntent = new Intent(this, RequestsActivity.class);
1703
1704                 // Add the block third-party requests status to the intent.
1705                 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1706
1707                 // Make it so.
1708                 startActivity(requestsIntent);
1709                 break;
1710
1711             case R.id.downloads:
1712                 // Launch the system Download Manager.
1713                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
1714
1715                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
1716                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1717
1718                 startActivity(downloadManagerIntent);
1719                 break;
1720
1721             case R.id.domains:
1722                 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
1723                 reapplyDomainSettingsOnRestart = true;
1724
1725                 // Launch the domains activity.
1726                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1727
1728                 // Add the extra information to the intent.
1729                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1730
1731                 // Get the current certificate.
1732                 SslCertificate sslCertificate = currentWebView.getCertificate();
1733
1734                 // Check to see if the SSL certificate is populated.
1735                 if (sslCertificate != null) {
1736                     // Extract the certificate to strings.
1737                     String issuedToCName = sslCertificate.getIssuedTo().getCName();
1738                     String issuedToOName = sslCertificate.getIssuedTo().getOName();
1739                     String issuedToUName = sslCertificate.getIssuedTo().getUName();
1740                     String issuedByCName = sslCertificate.getIssuedBy().getCName();
1741                     String issuedByOName = sslCertificate.getIssuedBy().getOName();
1742                     String issuedByUName = sslCertificate.getIssuedBy().getUName();
1743                     long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1744                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1745
1746                     // Add the certificate to the intent.
1747                     domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1748                     domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1749                     domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1750                     domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1751                     domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1752                     domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1753                     domainsIntent.putExtra("ssl_start_date", startDateLong);
1754                     domainsIntent.putExtra("ssl_end_date", endDateLong);
1755                 }
1756
1757                 // Check to see if the current IP addresses have been received.
1758                 if (currentWebView.hasCurrentIpAddresses()) {
1759                     // Add the current IP addresses to the intent.
1760                     domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1761                 }
1762
1763                 // Make it so.
1764                 startActivity(domainsIntent);
1765                 break;
1766
1767             case R.id.settings:
1768                 // Set the flag to reapply app settings on restart when returning from Settings.
1769                 reapplyAppSettingsOnRestart = true;
1770
1771                 // Set the flag to reapply the domain settings on restart when returning from Settings.
1772                 reapplyDomainSettingsOnRestart = true;
1773
1774                 // Launch the settings activity.
1775                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
1776                 startActivity(settingsIntent);
1777                 break;
1778
1779             case R.id.import_export:
1780                 // Launch the import/export activity.
1781                 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
1782                 startActivity(importExportIntent);
1783                 break;
1784
1785             case R.id.logcat:
1786                 // Launch the logcat activity.
1787                 Intent logcatIntent = new Intent(this, LogcatActivity.class);
1788                 startActivity(logcatIntent);
1789                 break;
1790
1791             case R.id.guide:
1792                 // Launch `GuideActivity`.
1793                 Intent guideIntent = new Intent(this, GuideActivity.class);
1794                 startActivity(guideIntent);
1795                 break;
1796
1797             case R.id.about:
1798                 // Create an intent to launch the about activity.
1799                 Intent aboutIntent = new Intent(this, AboutActivity.class);
1800
1801                 // Create a string array for the blocklist versions.
1802                 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],
1803                         ultraPrivacy.get(0).get(0)[0]};
1804
1805                 // Add the blocklist versions to the intent.
1806                 aboutIntent.putExtra("blocklist_versions", blocklistVersions);
1807
1808                 // Make it so.
1809                 startActivity(aboutIntent);
1810                 break;
1811         }
1812
1813         // Get a handle for the drawer layout.
1814         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1815
1816         // Close the navigation drawer.
1817         drawerLayout.closeDrawer(GravityCompat.START);
1818         return true;
1819     }
1820
1821     @Override
1822     public void onPostCreate(Bundle savedInstanceState) {
1823         // Run the default commands.
1824         super.onPostCreate(savedInstanceState);
1825
1826         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
1827         actionBarDrawerToggle.syncState();
1828     }
1829
1830     @Override
1831     public void onConfigurationChanged(Configuration newConfig) {
1832         // Run the default commands.
1833         super.onConfigurationChanged(newConfig);
1834
1835         // Get the status bar pixel size.
1836         int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
1837         int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
1838
1839         // Get the resource density.
1840         float screenDensity = getResources().getDisplayMetrics().density;
1841
1842         // Recalculate the drawer header padding.
1843         drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
1844         drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
1845         drawerHeaderPaddingBottom = (int) (8 * screenDensity);
1846
1847         // Reload the ad for the free flavor if not in full screen mode.
1848         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
1849             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1850             AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1851         }
1852
1853         // `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:
1854         // https://code.google.com/p/android/issues/detail?id=20493#c8
1855         // ActivityCompat.invalidateOptionsMenu(this);
1856     }
1857
1858     @Override
1859     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
1860         // Store the hit test result.
1861         final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
1862
1863         // Create the URL strings.
1864         final String imageUrl;
1865         final String linkUrl;
1866
1867         // Get handles for the system managers.
1868         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1869         FragmentManager fragmentManager = getSupportFragmentManager();
1870         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1871
1872         // Remove the lint errors below that the clipboard manager might be null.
1873         assert clipboardManager != null;
1874
1875         // Process the link according to the type.
1876         switch (hitTestResult.getType()) {
1877             // `SRC_ANCHOR_TYPE` is a link.
1878             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1879                 // Get the target URL.
1880                 linkUrl = hitTestResult.getExtra();
1881
1882                 // Set the target URL as the title of the `ContextMenu`.
1883                 menu.setHeaderTitle(linkUrl);
1884
1885                 // Add an Open in New Tab entry.
1886                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
1887                     // Load the link URL in a new tab.
1888                     addNewTab(linkUrl);
1889                     return false;
1890                 });
1891
1892                 // Add an Open with App entry.
1893                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
1894                     openWithApp(linkUrl);
1895                     return false;
1896                 });
1897
1898                 // Add an Open with Browser entry.
1899                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
1900                     openWithBrowser(linkUrl);
1901                     return false;
1902                 });
1903
1904                 // Add a Copy URL entry.
1905                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
1906                     // Save the link URL in a `ClipData`.
1907                     ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
1908
1909                     // Set the `ClipData` as the clipboard's primary clip.
1910                     clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
1911                     return false;
1912                 });
1913
1914                 // Add a Download URL entry.
1915                 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
1916                     // Check if the download should be processed by an external app.
1917                     if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
1918                         openUrlWithExternalApp(linkUrl);
1919                     } else {  // Download with Android's download manager.
1920                         // Check to see if the storage permission has already been granted.
1921                         if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
1922                             // Store the variables for future use by `onRequestPermissionsResult()`.
1923                             downloadUrl = linkUrl;
1924                             downloadContentDisposition = "none";
1925                             downloadContentLength = -1;
1926
1927                             // Show a dialog if the user has previously denied the permission.
1928                             if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
1929                                 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1930                                 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1931
1932                                 // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
1933                                 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
1934                             } else {  // Show the permission request directly.
1935                                 // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
1936                                 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1937                             }
1938                         } else {  // The storage permission has already been granted.
1939                             // Get a handle for the download file alert dialog.
1940                             DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
1941
1942                             // Show the download file alert dialog.
1943                             downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
1944                         }
1945                     }
1946                     return false;
1947                 });
1948
1949                 // Add a Cancel entry, which by default closes the context menu.
1950                 menu.add(R.string.cancel);
1951                 break;
1952
1953             case WebView.HitTestResult.EMAIL_TYPE:
1954                 // Get the target URL.
1955                 linkUrl = hitTestResult.getExtra();
1956
1957                 // Set the target URL as the title of the `ContextMenu`.
1958                 menu.setHeaderTitle(linkUrl);
1959
1960                 // Add a Write Email entry.
1961                 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
1962                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1963                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1964
1965                     // Parse the url and set it as the data for the `Intent`.
1966                     emailIntent.setData(Uri.parse("mailto:" + linkUrl));
1967
1968                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
1969                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1970
1971                     // Make it so.
1972                     startActivity(emailIntent);
1973                     return false;
1974                 });
1975
1976                 // Add a Copy Email Address entry.
1977                 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
1978                     // Save the email address in a `ClipData`.
1979                     ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
1980
1981                     // Set the `ClipData` as the clipboard's primary clip.
1982                     clipboardManager.setPrimaryClip(srcEmailTypeClipData);
1983                     return false;
1984                 });
1985
1986                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1987                 menu.add(R.string.cancel);
1988                 break;
1989
1990             // `IMAGE_TYPE` is an image. `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.  Privacy Browser processes them the same.
1991             case WebView.HitTestResult.IMAGE_TYPE:
1992             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1993                 // Get the image URL.
1994                 imageUrl = hitTestResult.getExtra();
1995
1996                 // Set the image URL as the title of the context menu.
1997                 menu.setHeaderTitle(imageUrl);
1998
1999                 // Add an Open in New Tab entry.
2000                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2001                     // Load the image URL in a new tab.
2002                     addNewTab(imageUrl);
2003                     return false;
2004                 });
2005
2006                 // Add a View Image entry.
2007                 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2008                     loadUrl(imageUrl);
2009                     return false;
2010                 });
2011
2012                 // Add a `Download Image` entry.
2013                 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
2014                     // Check if the download should be processed by an external app.
2015                     if (sharedPreferences.getBoolean("download_with_external_app", false)) {  // Download with an external app.
2016                         openUrlWithExternalApp(imageUrl);
2017                     } else {  // Download with Android's download manager.
2018                         // Check to see if the storage permission has already been granted.
2019                         if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
2020                             // Store the image URL for use by `onRequestPermissionResult()`.
2021                             downloadImageUrl = imageUrl;
2022
2023                             // Show a dialog if the user has previously denied the permission.
2024                             if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
2025                                 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
2026                                 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
2027
2028                                 // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
2029                                 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
2030                             } else {  // Show the permission request directly.
2031                                 // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
2032                                 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
2033                             }
2034                         } else {  // The storage permission has already been granted.
2035                             // Get a handle for the download image alert dialog.
2036                             DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
2037
2038                             // Show the download image alert dialog.
2039                             downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
2040                         }
2041                     }
2042                     return false;
2043                 });
2044
2045                 // Add a `Copy URL` entry.
2046                 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
2047                     // Save the image URL in a `ClipData`.
2048                     ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2049
2050                     // Set the `ClipData` as the clipboard's primary clip.
2051                     clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2052                     return false;
2053                 });
2054
2055                 // Add an Open with App entry.
2056                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2057                     openWithApp(imageUrl);
2058                     return false;
2059                 });
2060
2061                 // Add an Open with Browser entry.
2062                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2063                     openWithBrowser(imageUrl);
2064                     return false;
2065                 });
2066
2067                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
2068                 menu.add(R.string.cancel);
2069                 break;
2070         }
2071     }
2072
2073     @Override
2074     public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2075         // Get a handle for the bookmarks list view.
2076         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2077
2078         // Get the views from the dialog fragment.
2079         EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
2080         EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
2081
2082         // Extract the strings from the edit texts.
2083         String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2084         String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2085
2086         // Create a favorite icon byte array output stream.
2087         ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2088
2089         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2090         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2091
2092         // Convert the favorite icon byte array stream to a byte array.
2093         byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2094
2095         // Display the new bookmark below the current items in the (0 indexed) list.
2096         int newBookmarkDisplayOrder = bookmarksListView.getCount();
2097
2098         // Create the bookmark.
2099         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2100
2101         // Update the bookmarks cursor with the current contents of this folder.
2102         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2103
2104         // Update the list view.
2105         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2106
2107         // Scroll to the new bookmark.
2108         bookmarksListView.setSelection(newBookmarkDisplayOrder);
2109     }
2110
2111     @Override
2112     public void onCreateBookmarkFolder(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2113         // Get a handle for the bookmarks list view.
2114         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2115
2116         // Get handles for the views in the dialog fragment.
2117         EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
2118         RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
2119         ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
2120
2121         // Get new folder name string.
2122         String folderNameString = createFolderNameEditText.getText().toString();
2123
2124         // Create a folder icon bitmap.
2125         Bitmap folderIconBitmap;
2126
2127         // Set the folder icon bitmap according to the dialog.
2128         if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
2129             // Get the default folder icon drawable.
2130             Drawable folderIconDrawable = folderIconImageView.getDrawable();
2131
2132             // Convert the folder icon drawable to a bitmap drawable.
2133             BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2134
2135             // Convert the folder icon bitmap drawable to a bitmap.
2136             folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2137         } else {  // Use the WebView favorite icon.
2138             // Copy the favorite icon bitmap to the folder icon bitmap.
2139             folderIconBitmap = favoriteIconBitmap;
2140         }
2141
2142         // Create a folder icon byte array output stream.
2143         ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2144
2145         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2146         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2147
2148         // Convert the folder icon byte array stream to a byte array.
2149         byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2150
2151         // Move all the bookmarks down one in the display order.
2152         for (int i = 0; i < bookmarksListView.getCount(); i++) {
2153             int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2154             bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2155         }
2156
2157         // Create the folder, which will be placed at the top of the `ListView`.
2158         bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2159
2160         // Update the bookmarks cursor with the current contents of this folder.
2161         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2162
2163         // Update the `ListView`.
2164         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2165
2166         // Scroll to the new folder.
2167         bookmarksListView.setSelection(0);
2168     }
2169
2170     @Override
2171     public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) {
2172         // Get handles for the views from `dialogFragment`.
2173         EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
2174         EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
2175         RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
2176
2177         // Store the bookmark strings.
2178         String bookmarkNameString = editBookmarkNameEditText.getText().toString();
2179         String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
2180
2181         // Update the bookmark.
2182         if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
2183             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
2184         } else {  // Update the bookmark using the `WebView` favorite icon.
2185             // Create a favorite icon byte array output stream.
2186             ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2187
2188             // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2189             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
2190
2191             // Convert the favorite icon byte array stream to a byte array.
2192             byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
2193
2194             //  Update the bookmark and the favorite icon.
2195             bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
2196         }
2197
2198         // Update the bookmarks cursor with the current contents of this folder.
2199         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2200
2201         // Update the list view.
2202         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2203     }
2204
2205     @Override
2206     public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
2207         // Get handles for the views from `dialogFragment`.
2208         EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
2209         RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
2210         RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
2211         ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
2212
2213         // Get the new folder name.
2214         String newFolderNameString = editFolderNameEditText.getText().toString();
2215
2216         // Check if the favorite icon has changed.
2217         if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
2218             // Update the name in the database.
2219             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2220         } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
2221             // Create the new folder icon Bitmap.
2222             Bitmap folderIconBitmap;
2223
2224             // Populate the new folder icon bitmap.
2225             if (defaultFolderIconRadioButton.isChecked()) {
2226                 // Get the default folder icon drawable.
2227                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2228
2229                 // Convert the folder icon drawable to a bitmap drawable.
2230                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2231
2232                 // Convert the folder icon bitmap drawable to a bitmap.
2233                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2234             } else {  // Use the `WebView` favorite icon.
2235                 // Copy the favorite icon bitmap to the folder icon bitmap.
2236                 folderIconBitmap = favoriteIconBitmap;
2237             }
2238
2239             // Create a folder icon byte array output stream.
2240             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2241
2242             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2243             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2244
2245             // Convert the folder icon byte array stream to a byte array.
2246             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2247
2248             // Update the folder icon in the database.
2249             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2250         } else {  // The folder icon and the name have changed.
2251             // Get the new folder icon `Bitmap`.
2252             Bitmap folderIconBitmap;
2253             if (defaultFolderIconRadioButton.isChecked()) {
2254                 // Get the default folder icon drawable.
2255                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2256
2257                 // Convert the folder icon drawable to a bitmap drawable.
2258                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2259
2260                 // Convert the folder icon bitmap drawable to a bitmap.
2261                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2262             } else {  // Use the `WebView` favorite icon.
2263                 // Copy the favorite icon bitmap to the folder icon bitmap.
2264                 folderIconBitmap = favoriteIconBitmap;
2265             }
2266
2267             // Create a folder icon byte array output stream.
2268             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2269
2270             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2271             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2272
2273             // Convert the folder icon byte array stream to a byte array.
2274             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2275
2276             // Update the folder name and icon in the database.
2277             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2278         }
2279
2280         // Update the bookmarks cursor with the current contents of this folder.
2281         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2282
2283         // Update the `ListView`.
2284         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2285     }
2286
2287     @Override
2288     public void onCloseDownloadLocationPermissionDialog(int downloadType) {
2289         switch (downloadType) {
2290             case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
2291                 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
2292                 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
2293                 break;
2294
2295             case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
2296                 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
2297                 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
2298                 break;
2299         }
2300     }
2301
2302     @Override
2303     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
2304         // Get a handle for the fragment manager.
2305         FragmentManager fragmentManager = getSupportFragmentManager();
2306
2307         switch (requestCode) {
2308             case DOWNLOAD_FILE_REQUEST_CODE:
2309                 // Show the download file alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
2310                 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
2311
2312                 // On API 23, displaying the fragment must be delayed or the app will crash.
2313                 if (Build.VERSION.SDK_INT == 23) {
2314                     new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
2315                 } else {
2316                     downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
2317                 }
2318
2319                 // Reset the download variables.
2320                 downloadUrl = "";
2321                 downloadContentDisposition = "";
2322                 downloadContentLength = 0;
2323                 break;
2324
2325             case DOWNLOAD_IMAGE_REQUEST_CODE:
2326                 // Show the download image alert dialog.  When the dialog closes, the correct command will be used based on the permission status.
2327                 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
2328
2329                 // On API 23, displaying the fragment must be delayed or the app will crash.
2330                 if (Build.VERSION.SDK_INT == 23) {
2331                     new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
2332                 } else {
2333                     downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
2334                 }
2335
2336                 // Reset the image URL variable.
2337                 downloadImageUrl = "";
2338                 break;
2339         }
2340     }
2341
2342     @Override
2343     public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
2344         // Download the image if it has an HTTP or HTTPS URI.
2345         if (imageUrl.startsWith("http")) {
2346             // Get a handle for the system `DOWNLOAD_SERVICE`.
2347             DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
2348
2349             // Parse `imageUrl`.
2350             DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
2351
2352             // Get a handle for the cookie manager.
2353             CookieManager cookieManager = CookieManager.getInstance();
2354
2355             // Pass cookies to download manager if cookies are enabled.  This is required to download images from websites that require a login.
2356             // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
2357             if (cookieManager.acceptCookie()) {
2358                 // Get the cookies for `imageUrl`.
2359                 String cookies = cookieManager.getCookie(imageUrl);
2360
2361                 // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
2362                 downloadRequest.addRequestHeader("Cookie", cookies);
2363             }
2364
2365             // Get the file name from the dialog fragment.
2366             EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
2367             String imageName = downloadImageNameEditText.getText().toString();
2368
2369             // Specify the download location.
2370             if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
2371                 // Download to the public download directory.
2372                 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
2373             } else {  // External write permission denied.
2374                 // Download to the app's external download directory.
2375                 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
2376             }
2377
2378             // Allow `MediaScanner` to index the download if it is a media file.
2379             downloadRequest.allowScanningByMediaScanner();
2380
2381             // Add the URL as the description for the download.
2382             downloadRequest.setDescription(imageUrl);
2383
2384             // Show the download notification after the download is completed.
2385             downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
2386
2387             // Remove the lint warning below that `downloadManager` might be `null`.
2388             assert downloadManager != null;
2389
2390             // Initiate the download.
2391             downloadManager.enqueue(downloadRequest);
2392         } else {  // The image is not an HTTP or HTTPS URI.
2393             Snackbar.make(currentWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
2394         }
2395     }
2396
2397     @Override
2398     public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
2399         // Download the file if it has an HTTP or HTTPS URI.
2400         if (downloadUrl.startsWith("http")) {
2401             // Get a handle for the system `DOWNLOAD_SERVICE`.
2402             DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
2403
2404             // Parse `downloadUrl`.
2405             DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
2406
2407             // Get a handle for the cookie manager.
2408             CookieManager cookieManager = CookieManager.getInstance();
2409
2410             // Pass cookies to download manager if cookies are enabled.  This is required to download files from websites that require a login.
2411             // Code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
2412             if (cookieManager.acceptCookie()) {
2413                 // Get the cookies for `downloadUrl`.
2414                 String cookies = cookieManager.getCookie(downloadUrl);
2415
2416                 // Add the cookies to `downloadRequest`.  In the HTTP request header, cookies are named `Cookie`.
2417                 downloadRequest.addRequestHeader("Cookie", cookies);
2418             }
2419
2420             // Get the file name from the dialog fragment.
2421             EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
2422             String fileName = downloadFileNameEditText.getText().toString();
2423
2424             // Specify the download location.
2425             if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {  // External write permission granted.
2426                 // Download to the public download directory.
2427                 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
2428             } else {  // External write permission denied.
2429                 // Download to the app's external download directory.
2430                 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
2431             }
2432
2433             // Allow `MediaScanner` to index the download if it is a media file.
2434             downloadRequest.allowScanningByMediaScanner();
2435
2436             // Add the URL as the description for the download.
2437             downloadRequest.setDescription(downloadUrl);
2438
2439             // Show the download notification after the download is completed.
2440             downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
2441
2442             // Remove the lint warning below that `downloadManager` might be `null`.
2443             assert downloadManager != null;
2444
2445             // Initiate the download.
2446             downloadManager.enqueue(downloadRequest);
2447         } else {  // The download is not an HTTP or HTTPS URI.
2448             Snackbar.make(currentWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
2449         }
2450     }
2451
2452     // Override `onBackPressed` to handle the navigation drawer and and the WebView.
2453     @Override
2454     public void onBackPressed() {
2455         // Get a handle for the drawer layout and the tab layout.
2456         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2457         TabLayout tabLayout = findViewById(R.id.tablayout);
2458
2459         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
2460             // Close the navigation drawer.
2461             drawerLayout.closeDrawer(GravityCompat.START);
2462         } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
2463             if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
2464                 // close the bookmarks drawer.
2465                 drawerLayout.closeDrawer(GravityCompat.END);
2466             } else {  // A subfolder is displayed.
2467                 // Place the former parent folder in `currentFolder`.
2468                 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
2469
2470                 // Load the new folder.
2471                 loadBookmarksFolder();
2472             }
2473         } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
2474             // Reset the current domain name so that navigation works if third-party requests are blocked.
2475             currentWebView.resetCurrentDomainName();
2476
2477             // Set navigating history so that the domain settings are applied when the new URL is loaded.
2478             currentWebView.setNavigatingHistory(true);
2479
2480             // Go back.
2481             currentWebView.goBack();
2482         } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
2483             // Close the current tab.
2484             closeCurrentTab();
2485         } else {  // There isn't anything to do in Privacy Browser.
2486             // Run the default commands.
2487             super.onBackPressed();
2488
2489             // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
2490             System.exit(0);
2491         }
2492     }
2493
2494     // Process the results of an upload file chooser.  Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
2495     @Override
2496     public void onActivityResult(int requestCode, int resultCode, Intent data) {
2497         // File uploads only work on API >= 21.
2498         if (Build.VERSION.SDK_INT >= 21) {
2499             // Pass the file to the WebView.
2500             fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
2501         }
2502     }
2503
2504     private void loadUrlFromTextBox() {
2505         // Get a handle for the URL edit text.
2506         EditText urlEditText = findViewById(R.id.url_edittext);
2507
2508         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
2509         String unformattedUrlString = urlEditText.getText().toString().trim();
2510
2511         // Initialize the formatted URL string.
2512         String url = "";
2513
2514         // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
2515         if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
2516             // Load the entire content URL.
2517             url = unformattedUrlString;
2518         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2519                 unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
2520             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
2521             if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
2522                 unformattedUrlString = "https://" + unformattedUrlString;
2523             }
2524
2525             // Initialize `unformattedUrl`.
2526             URL unformattedUrl = null;
2527
2528             // 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.
2529             try {
2530                 unformattedUrl = new URL(unformattedUrlString);
2531             } catch (MalformedURLException e) {
2532                 e.printStackTrace();
2533             }
2534
2535             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
2536             String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
2537             String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
2538             String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
2539             String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
2540             String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
2541
2542             // Build the URI.
2543             Uri.Builder uri = new Uri.Builder();
2544             uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
2545
2546             // Decode the URI as a UTF-8 string in.
2547             try {
2548                 url = URLDecoder.decode(uri.build().toString(), "UTF-8");
2549             } catch (UnsupportedEncodingException exception) {
2550                 // Do nothing.  The formatted URL string will remain blank.
2551             }
2552         } else if (!unformattedUrlString.isEmpty()){  // This is not a URL, but rather a search string.
2553             // Create an encoded URL String.
2554             String encodedUrlString;
2555
2556             // Sanitize the search input.
2557             try {
2558                 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
2559             } catch (UnsupportedEncodingException exception) {
2560                 encodedUrlString = "";
2561             }
2562
2563             // Add the base search URL.
2564             url = searchURL + encodedUrlString;
2565         }
2566
2567         // 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.
2568         urlEditText.clearFocus();
2569
2570         // Make it so.
2571         loadUrl(url);
2572     }
2573
2574     private void loadUrl(String url) {
2575         // Sanitize the URL.
2576         url = sanitizeUrl(url);
2577
2578         // Apply the domain settings.
2579         applyDomainSettings(currentWebView, url, true, false);
2580
2581         // Load the URL.
2582         currentWebView.loadUrl(url, customHeaders);
2583     }
2584
2585     public void findPreviousOnPage(View view) {
2586         // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
2587         currentWebView.findNext(false);
2588     }
2589
2590     public void findNextOnPage(View view) {
2591         // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
2592         currentWebView.findNext(true);
2593     }
2594
2595     public void closeFindOnPage(View view) {
2596         // Get a handle for the views.
2597         Toolbar toolbar = findViewById(R.id.toolbar);
2598         LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2599         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2600
2601         // Delete the contents of `find_on_page_edittext`.
2602         findOnPageEditText.setText(null);
2603
2604         // Clear the highlighted phrases if the WebView is not null.
2605         if (currentWebView != null) {
2606             currentWebView.clearMatches();
2607         }
2608
2609         // Hide the find on page linear layout.
2610         findOnPageLinearLayout.setVisibility(View.GONE);
2611
2612         // Show the toolbar.
2613         toolbar.setVisibility(View.VISIBLE);
2614
2615         // Get a handle for the input method manager.
2616         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2617
2618         // Remove the lint warning below that the input method manager might be null.
2619         assert inputMethodManager != null;
2620
2621         // Hide the keyboard.
2622         inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
2623     }
2624
2625     private void applyAppSettings() {
2626         // 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.
2627         if (webViewDefaultUserAgent == null) {
2628             initializeApp();
2629         }
2630
2631         // Get a handle for the shared preferences.
2632         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2633
2634         // Store the values from the shared preferences in variables.
2635         incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
2636         boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
2637         sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
2638         sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
2639         sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
2640         proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
2641         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
2642         hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
2643         scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
2644
2645         // Get handles for the views that need to be modified.
2646         FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
2647         AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
2648         ActionBar actionBar = getSupportActionBar();
2649         Toolbar toolbar = findViewById(R.id.toolbar);
2650         LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2651         LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
2652         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
2653
2654         // Remove the incorrect lint warning below that the action bar might be null.
2655         assert actionBar != null;
2656
2657         // Apply the proxy through Orbot settings.
2658         applyProxyThroughOrbot(false);
2659
2660         // Set Do Not Track status.
2661         if (doNotTrackEnabled) {
2662             customHeaders.put("DNT", "1");
2663         } else {
2664             customHeaders.remove("DNT");
2665         }
2666
2667         // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
2668         CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
2669         AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
2670         AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
2671         AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
2672
2673         // Add the scrolling behavior to the layout parameters.
2674         if (scrollAppBar) {
2675             // Enable scrolling of the app bar.
2676             swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
2677             toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
2678             findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
2679             tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
2680         } else {
2681             // Disable scrolling of the app bar.
2682             swipeRefreshLayoutParams.setBehavior(null);
2683             toolbarLayoutParams.setScrollFlags(0);
2684             findOnPageLayoutParams.setScrollFlags(0);
2685             tabsLayoutParams.setScrollFlags(0);
2686
2687             // Expand the app bar if it is currently collapsed.
2688             appBarLayout.setExpanded(true);
2689         }
2690
2691         // Apply the modified layout parameters.
2692         swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
2693         toolbar.setLayoutParams(toolbarLayoutParams);
2694         findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
2695         tabsLinearLayout.setLayoutParams(tabsLayoutParams);
2696
2697         // Set the app bar scrolling for each WebView.
2698         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
2699             // Get the WebView tab fragment.
2700             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
2701
2702             // Get the fragment view.
2703             View fragmentView = webViewTabFragment.getView();
2704
2705             // Only modify the WebViews if they exist.
2706             if (fragmentView != null) {
2707                 // Get the nested scroll WebView from the tab fragment.
2708                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
2709
2710                 // Set the app bar scrolling.
2711                 nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
2712             }
2713         }
2714
2715         // Update the full screen browsing mode settings.
2716         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
2717             // Update the visibility of the app bar, which might have changed in the settings.
2718             if (hideAppBar) {
2719                 // Hide the tab linear layout.
2720                 tabsLinearLayout.setVisibility(View.GONE);
2721
2722                 // Hide the action bar.
2723                 actionBar.hide();
2724             } else {
2725                 // Show the tab linear layout.
2726                 tabsLinearLayout.setVisibility(View.VISIBLE);
2727
2728                 // Show the action bar.
2729                 actionBar.show();
2730             }
2731
2732             // Hide the banner ad in the free flavor.
2733             if (BuildConfig.FLAVOR.contentEquals("free")) {
2734                 AdHelper.hideAd(findViewById(R.id.adview));
2735             }
2736
2737             // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
2738             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2739
2740             /* Hide the system bars.
2741              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2742              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
2743              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2744              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2745              */
2746             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
2747                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2748         } else {  // Privacy Browser is not in full screen browsing mode.
2749             // 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.
2750             inFullScreenBrowsingMode = false;
2751
2752             // Show the tab linear layout.
2753             tabsLinearLayout.setVisibility(View.VISIBLE);
2754
2755             // Show the action bar.
2756             actionBar.show();
2757
2758             // Show the banner ad in the free flavor.
2759             if (BuildConfig.FLAVOR.contentEquals("free")) {
2760                 // Initialize the ads.  If this isn't the first run, `loadAd()` will be automatically called instead.
2761                 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
2762             }
2763
2764             // Remove the `SYSTEM_UI` flags from the root frame layout.
2765             rootFrameLayout.setSystemUiVisibility(0);
2766
2767             // Add the translucent status flag.
2768             getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2769         }
2770     }
2771
2772     private void initializeApp() {
2773         // Get a handle for the shared preferences.
2774         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2775
2776         // Get the theme preference.
2777         boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
2778
2779         // Get a handle for the input method.
2780         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2781
2782         // Remove the lint warning below that the input method manager might be null.
2783         assert inputMethodManager != null;
2784
2785         // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
2786         redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
2787         initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
2788         finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
2789
2790         // Get handles for the URL views.
2791         EditText urlEditText = findViewById(R.id.url_edittext);
2792
2793         // Remove the formatting from the URL edit text when the user is editing the text.
2794         urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
2795             if (hasFocus) {  // The user is editing the URL text box.
2796                 // Remove the highlighting.
2797                 urlEditText.getText().removeSpan(redColorSpan);
2798                 urlEditText.getText().removeSpan(initialGrayColorSpan);
2799                 urlEditText.getText().removeSpan(finalGrayColorSpan);
2800             } else {  // The user has stopped editing the URL text box.
2801                 // Move to the beginning of the string.
2802                 urlEditText.setSelection(0);
2803
2804                 // Reapply the highlighting.
2805                 highlightUrlText();
2806             }
2807         });
2808
2809         // Set the go button on the keyboard to load the URL in `urlTextBox`.
2810         urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
2811             // If the event is a key-down event on the `enter` button, load the URL.
2812             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
2813                 // Load the URL into the mainWebView and consume the event.
2814                 loadUrlFromTextBox();
2815
2816                 // If the enter key was pressed, consume the event.
2817                 return true;
2818             } else {
2819                 // If any other key was pressed, do not consume the event.
2820                 return false;
2821             }
2822         });
2823
2824         // Initialize the Orbot status and the waiting for Orbot trackers.
2825         orbotStatus = "unknown";
2826         waitingForOrbot = false;
2827
2828         // Create an Orbot status `BroadcastReceiver`.
2829         orbotStatusBroadcastReceiver = new BroadcastReceiver() {
2830             @Override
2831             public void onReceive(Context context, Intent intent) {
2832                 // Store the content of the status message in `orbotStatus`.
2833                 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
2834
2835                 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
2836                 if (orbotStatus.equals("ON") && waitingForOrbot) {
2837                     // Reset the waiting for Orbot status.
2838                     waitingForOrbot = false;
2839
2840                     // Get the intent that started the app.
2841                     Intent launchingIntent = getIntent();
2842
2843                     // Get the information from the intent.
2844                     String launchingIntentAction = launchingIntent.getAction();
2845                     Uri launchingIntentUriData = launchingIntent.getData();
2846
2847                     // If the intent action is a web search, perform the search.
2848                     if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
2849                         // Create an encoded URL string.
2850                         String encodedUrlString;
2851
2852                         // Sanitize the search input and convert it to a search.
2853                         try {
2854                             encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
2855                         } catch (UnsupportedEncodingException exception) {
2856                             encodedUrlString = "";
2857                         }
2858
2859                         // Load the completed search URL.
2860                         loadUrl(searchURL + encodedUrlString);
2861                     } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
2862                         // Load the URL from the intent.
2863                         loadUrl(launchingIntentUriData.toString());
2864                     } else {  // The is no URL in the intent.
2865                         // Select the homepage based on the proxy through Orbot status.
2866                         if (proxyThroughOrbot) {
2867                             // Load the Tor homepage.
2868                             loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
2869                         } else {
2870                             // Load the normal homepage.
2871                             loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
2872                         }
2873                     }
2874                 }
2875             }
2876         };
2877
2878         // Register `orbotStatusBroadcastReceiver` on `this` context.
2879         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
2880
2881         // Get handles for views that need to be modified.
2882         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2883         NavigationView navigationView = findViewById(R.id.navigationview);
2884         TabLayout tabLayout = findViewById(R.id.tablayout);
2885         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
2886         ViewPager webViewPager = findViewById(R.id.webviewpager);
2887         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2888         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
2889         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
2890         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
2891         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2892
2893         // Listen for touches on the navigation menu.
2894         navigationView.setNavigationItemSelectedListener(this);
2895
2896         // Get handles for the navigation menu and the back and forward menu items.  The menu is zero-based.
2897         Menu navigationMenu = navigationView.getMenu();
2898         MenuItem navigationBackMenuItem = navigationMenu.getItem(2);
2899         MenuItem navigationForwardMenuItem = navigationMenu.getItem(3);
2900         MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4);
2901         MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5);
2902
2903         // Update the web view pager every time a tab is modified.
2904         webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
2905             @Override
2906             public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
2907                 // Do nothing.
2908             }
2909
2910             @Override
2911             public void onPageSelected(int position) {
2912                 // Close the find on page bar if it is open.
2913                 closeFindOnPage(null);
2914
2915                 // Set the current WebView.
2916                 setCurrentWebView(position);
2917
2918                 // 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.
2919                 if (tabLayout.getSelectedTabPosition() != position) {
2920                     // Create a handler to select the tab.
2921                     Handler selectTabHandler = new Handler();
2922
2923                     // Create a runnable to select the tab.
2924                     Runnable selectTabRunnable = () -> {
2925                         // Get a handle for the tab.
2926                         TabLayout.Tab tab = tabLayout.getTabAt(position);
2927
2928                         // Assert that the tab is not null.
2929                         assert tab != null;
2930
2931                         // Select the tab.
2932                         tab.select();
2933                     };
2934
2935                     // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated.
2936                     selectTabHandler.postDelayed(selectTabRunnable, 150);
2937                 }
2938             }
2939
2940             @Override
2941             public void onPageScrollStateChanged(int state) {
2942                 // Do nothing.
2943             }
2944         });
2945
2946         // Display the View SSL Certificate dialog when the currently selected tab is reselected.
2947         tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
2948             @Override
2949             public void onTabSelected(TabLayout.Tab tab) {
2950                 // Select the same page in the view pager.
2951                 webViewPager.setCurrentItem(tab.getPosition());
2952             }
2953
2954             @Override
2955             public void onTabUnselected(TabLayout.Tab tab) {
2956                 // Do nothing.
2957             }
2958
2959             @Override
2960             public void onTabReselected(TabLayout.Tab tab) {
2961                 // Instantiate the View SSL Certificate dialog.
2962                 DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());