e08fa8fa764a13d8e0ce06ce81b405dfc464d9ae
[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.WebBackForwardList;
75 import android.webkit.WebChromeClient;
76 import android.webkit.WebResourceResponse;
77 import android.webkit.WebSettings;
78 import android.webkit.WebStorage;
79 import android.webkit.WebView;
80 import android.webkit.WebViewClient;
81 import android.webkit.WebViewDatabase;
82 import android.widget.ArrayAdapter;
83 import android.widget.CursorAdapter;
84 import android.widget.EditText;
85 import android.widget.FrameLayout;
86 import android.widget.ImageView;
87 import android.widget.LinearLayout;
88 import android.widget.ListView;
89 import android.widget.ProgressBar;
90 import android.widget.RadioButton;
91 import android.widget.RelativeLayout;
92 import android.widget.TextView;
93
94 import androidx.annotation.NonNull;
95 import androidx.appcompat.app.ActionBar;
96 import androidx.appcompat.app.ActionBarDrawerToggle;
97 import androidx.appcompat.app.AppCompatActivity;
98 import androidx.appcompat.widget.Toolbar;
99 import androidx.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.floatingactionbutton.FloatingActionButton;
109 import com.google.android.material.navigation.NavigationView;
110 import com.google.android.material.snackbar.Snackbar;
111 import com.google.android.material.tabs.TabLayout;
112
113 import com.stoutner.privacybrowser.BuildConfig;
114 import com.stoutner.privacybrowser.R;
115 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
116 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
117 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
118 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
119 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
120 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
121 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
122 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
123 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
124 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
125 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
126 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
127 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
128 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
129 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
130 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
131 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
132 import com.stoutner.privacybrowser.helpers.AdHelper;
133 import com.stoutner.privacybrowser.helpers.BlockListHelper;
134 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
135 import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
136 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
137 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
138 import com.stoutner.privacybrowser.views.NestedScrollWebView;
139
140 import java.io.ByteArrayInputStream;
141 import java.io.ByteArrayOutputStream;
142 import java.io.File;
143 import java.io.IOException;
144 import java.io.UnsupportedEncodingException;
145 import java.net.MalformedURLException;
146 import java.net.URL;
147 import java.net.URLDecoder;
148 import java.net.URLEncoder;
149 import java.util.ArrayList;
150 import java.util.Date;
151 import java.util.HashMap;
152 import java.util.HashSet;
153 import java.util.List;
154 import java.util.Map;
155 import java.util.Set;
156
157 // TODO.  Store up reloads for tabs that are not visible.
158 // TODO.  New tabs are white in dark mode.
159
160 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
161 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
162         DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
163         EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, WebViewTabFragment.NewTabListener,
164         PinnedMismatchDialog.PinnedMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
165
166     // `darkTheme` is public static so it can be accessed from everywhere.
167     public static boolean darkTheme;
168
169     // `allowScreenshots` is public static so it can be accessed from everywhere.  It is also used in `onCreate()`.
170     public static boolean allowScreenshots;
171
172     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
173     public static String orbotStatus;
174
175     // The WebView pager adapter is accessed from `PinnedMismatchDialog`.  It is also used in `onCreate()`, `onResume()`, and `addTab()`.
176     public static WebViewPagerAdapter webViewPagerAdapter;
177
178     // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`.  It is used in `onRestart()`
179     public static boolean reloadOnRestart;
180
181     // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`.  They are used in `onRestart()`.
182     public static boolean loadUrlOnRestart;
183     public static String urlToLoadOnRestart;
184
185     // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
186     public static boolean restartFromBookmarksActivity;
187
188     // The blocklist versions are public static so they can be accessed from `AboutTabFragment`.  They are also used in `onCreate()`.
189     public static String easyListVersion;
190     public static String easyPrivacyVersion;
191     public static String fanboysAnnoyanceVersion;
192     public static String fanboysSocialVersion;
193     public static String ultraPrivacyVersion;
194
195     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
196     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
197     public static String currentBookmarksFolder;
198
199     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
200     public final static int UNRECOGNIZED_USER_AGENT = -1;
201     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
202     public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
203     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
204     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
205     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
206
207
208
209     // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
210     private boolean navigatingHistory;  // TODO.
211
212     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
213     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`.
214     private NestedScrollWebView currentWebView;
215
216     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
217     private final Map<String, String> customHeaders = new HashMap<>();
218
219     // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
220     private boolean firstPartyCookiesEnabled;
221
222     // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyProxyThroughOrbot()`.
223     private String homepage;  // TODO ?
224
225     // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`.
226     private String searchURL;  // TODO ?
227
228     // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
229     private Menu optionsMenu;
230
231     // TODO.  This could probably be removed.
232     // The blocklist helper is used in `onCreate()` and `WebViewPagerAdapter`.
233     private BlockListHelper blockListHelper;
234
235     // The blocklists are populated in `onCreate()` and accessed from `WebViewPagerAdapter`.
236     private ArrayList<List<String[]>> easyList;
237     private ArrayList<List<String[]>> easyPrivacy;
238     private ArrayList<List<String[]>> fanboysAnnoyanceList;
239     private ArrayList<List<String[]>> fanboysSocialList;
240     private ArrayList<List<String[]>> ultraPrivacy;
241
242     // The blocklist menu items are used in `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, and `initializeWebView()`.  // TODO.
243     private MenuItem blocklistsMenuItem;
244     private MenuItem easyListMenuItem;
245     private MenuItem easyPrivacyMenuItem;
246     private MenuItem fanboysAnnoyanceListMenuItem;
247     private MenuItem fanboysSocialBlockingListMenuItem;
248     private MenuItem ultraPrivacyMenuItem;
249     private MenuItem blockAllThirdPartyRequestsMenuItem;
250
251     // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
252     private String webViewDefaultUserAgent;
253
254     // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
255     private boolean proxyThroughOrbot;
256
257     // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
258     private boolean incognitoModeEnabled;  // TODO.
259
260     // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
261     private boolean fullScreenBrowsingModeEnabled;  // TODO.
262
263     // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
264     private boolean inFullScreenBrowsingMode;
265
266     // Hide app bar is used in `onCreate()` and `applyAppSettings()`.
267     private boolean hideAppBar;  // TODO.
268
269     // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
270     private boolean reapplyDomainSettingsOnRestart;
271
272     // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
273     private boolean reapplyAppSettingsOnRestart;
274
275     // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
276     private boolean displayingFullScreenVideo;
277
278     // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`.
279     private boolean downloadWithExternalApp;  // TODO.
280
281     // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
282     private BroadcastReceiver orbotStatusBroadcastReceiver;
283
284     // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
285     private boolean waitingForOrbot;
286
287     // `domainSettingsJavaScriptEnabled` is used in `onOptionsItemSelected()` and `applyDomainSettings()`.
288     private Boolean domainSettingsJavaScriptEnabled;  // TODO.
289
290     // `waitingForOrbotHtmlString` is used in `onCreate()` and `applyProxyThroughOrbot()`.
291     private String waitingForOrbotHtmlString;  // TODO.
292
293     // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
294     private String privateDataDirectoryString;  // TODO.
295
296     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
297     private ActionBarDrawerToggle actionBarDrawerToggle;
298
299     // The color spans are used in `onCreate()` and `highlightUrlText()`.
300     private ForegroundColorSpan redColorSpan;
301     private ForegroundColorSpan initialGrayColorSpan;
302     private ForegroundColorSpan finalGrayColorSpan;
303
304     // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
305     private int drawerHeaderPaddingLeftAndRight;
306     private int drawerHeaderPaddingTop;
307     private int drawerHeaderPaddingBottom;
308
309     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
310     private SslErrorHandler sslErrorHandler;  // TODO.
311
312     // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
313     private static HttpAuthHandler httpAuthHandler;  // TODO.
314
315     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
316     // and `loadBookmarksFolder()`.
317     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
318
319     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
320     private Cursor bookmarksCursor;
321
322     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
323     private CursorAdapter bookmarksCursorAdapter;
324
325     // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
326     private String oldFolderNameString;
327
328     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
329     private ValueCallback<Uri[]> fileChooserCallback;
330
331     // The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
332     private String downloadUrl;
333     private String downloadContentDisposition;
334     private long downloadContentLength;
335
336     // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
337     private String downloadImageUrl;
338
339     // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`.
340     private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
341     private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
342
343     @Override
344     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
345     @SuppressLint("ClickableViewAccessibility")
346     protected void onCreate(Bundle savedInstanceState) {
347         // Get a handle for the shared preferences.
348         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
349
350         // Get the theme and screenshot preferences.
351         darkTheme = sharedPreferences.getBoolean("dark_theme", false);
352         allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
353
354         // Disable screenshots if not allowed.
355         if (!allowScreenshots) {
356             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
357         }
358
359         // Set the activity theme.
360         if (darkTheme) {
361             setTheme(R.style.PrivacyBrowserDark);
362         } else {
363             setTheme(R.style.PrivacyBrowserLight);
364         }
365
366         // Run the default commands.
367         super.onCreate(savedInstanceState);
368
369         // Set the content view.
370         setContentView(R.layout.main_framelayout);
371
372         // Get a handle for the input method.
373         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
374
375         // Remove the lint warning below that the input method manager might be null.
376         assert inputMethodManager != null;
377
378         // Get a handle for the toolbar.
379         Toolbar toolbar = findViewById(R.id.toolbar);
380
381         // Set the action bar.  `SupportActionBar` must be used until the minimum API is >= 21.
382         setSupportActionBar(toolbar);
383
384         // Get a handle for the action bar.
385         ActionBar actionBar = getSupportActionBar();
386
387         // This is needed to get rid of the Android Studio warning that the action bar might be null.
388         assert actionBar != null;
389
390         // Add the custom layout, which shows the URL text bar.
391         actionBar.setCustomView(R.layout.url_app_bar);
392         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
393
394         // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
395         redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
396         initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
397         finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
398
399         // Get handles for the URL views.
400         EditText urlEditText = findViewById(R.id.url_edittext);
401
402         // Remove the formatting from `urlTextBar` when the user is editing the text.
403         urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
404             if (hasFocus) {  // The user is editing the URL text box.
405                 // Remove the highlighting.
406                 urlEditText.getText().removeSpan(redColorSpan);
407                 urlEditText.getText().removeSpan(initialGrayColorSpan);
408                 urlEditText.getText().removeSpan(finalGrayColorSpan);
409             } else {  // The user has stopped editing the URL text box.
410                 // Move to the beginning of the string.
411                 urlEditText.setSelection(0);
412
413                 // Reapply the highlighting.
414                 highlightUrlText();
415             }
416         });
417
418         // Set the go button on the keyboard to load the URL in `urlTextBox`.
419         urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
420             // If the event is a key-down event on the `enter` button, load the URL.
421             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
422                 // Load the URL into the mainWebView and consume the event.
423                 loadUrlFromTextBox();
424
425                 // If the enter key was pressed, consume the event.
426                 return true;
427             } else {
428                 // If any other key was pressed, do not consume the event.
429                 return false;
430             }
431         });
432
433         // Set `waitingForOrbotHTMLString`.
434         waitingForOrbotHtmlString = "<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>";
435
436         // Initialize the Orbot status and the waiting for Orbot trackers.
437         orbotStatus = "unknown";
438         waitingForOrbot = false;
439
440         // Create an Orbot status `BroadcastReceiver`.
441         orbotStatusBroadcastReceiver = new BroadcastReceiver() {
442             @Override
443             public void onReceive(Context context, Intent intent) {
444                 // Store the content of the status message in `orbotStatus`.
445                 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
446
447                 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
448                 if (orbotStatus.equals("ON") && waitingForOrbot) {
449                     // Reset the waiting for Orbot status.
450                     waitingForOrbot = false;
451
452                     // Get the intent that started the app.
453                     Intent launchingIntent = getIntent();
454
455                     // Get the information from the intent.
456                     String launchingIntentAction = launchingIntent.getAction();
457                     Uri launchingIntentUriData = launchingIntent.getData();
458
459                     // If the intent action is a web search, perform the search.
460                     if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
461                         // Create an encoded URL string.
462                         String encodedUrlString;
463
464                         // Sanitize the search input and convert it to a search.
465                         try {
466                             encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
467                         } catch (UnsupportedEncodingException exception) {
468                             encodedUrlString = "";
469                         }
470
471                         // Load the completed search URL.
472                         loadUrl(searchURL + encodedUrlString);
473                     } else if (launchingIntentUriData != null){  // Check to see if the intent contains a new URL.
474                         // Load the URL from the intent.
475                         loadUrl(launchingIntentUriData.toString());
476                     } else {  // The is no URL in the intent.
477                         // Load the homepage.
478                         loadUrl(homepage);
479                     }
480                 }
481             }
482         };
483
484         // Register `orbotStatusBroadcastReceiver` on `this` context.
485         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
486
487         // Instantiate the block list helper.
488         blockListHelper = new BlockListHelper();
489
490         // Parse the block lists.
491         easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
492         easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
493         fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
494         fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
495         ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
496
497         // Store the list versions.
498         easyListVersion = easyList.get(0).get(0)[0];
499         easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
500         fanboysAnnoyanceVersion = fanboysAnnoyanceList.get(0).get(0)[0];
501         fanboysSocialVersion = fanboysSocialList.get(0).get(0)[0];
502         ultraPrivacyVersion = ultraPrivacy.get(0).get(0)[0];
503
504         // Get handles for views that need to be modified.
505         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
506         NavigationView navigationView = findViewById(R.id.navigationview);
507         TabLayout tabLayout = findViewById(R.id.tablayout);
508         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
509         ViewPager webViewPager = findViewById(R.id.webviewpager);
510         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
511         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
512         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
513         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
514         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
515
516         // Listen for touches on the navigation menu.
517         navigationView.setNavigationItemSelectedListener(this);
518
519         // Get handles for the navigation menu and the back and forward menu items.  The menu is zero-based.
520         Menu navigationMenu = navigationView.getMenu();
521         MenuItem navigationCloseTabMenuItem = navigationMenu.getItem(0);
522         MenuItem navigationBackMenuItem = navigationMenu.getItem(3);
523         MenuItem navigationForwardMenuItem = navigationMenu.getItem(4);
524         MenuItem navigationHistoryMenuItem = navigationMenu.getItem(5);
525         MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
526
527         // Initialize the web view pager adapter.
528         webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
529
530         // Set the pager adapter on the web view pager.
531         webViewPager.setAdapter(webViewPagerAdapter);
532
533         // Store up to 100 tabs in memory.
534         webViewPager.setOffscreenPageLimit(100);
535
536         // Update the web view pager every time a tab is modified.
537         webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
538             @Override
539             public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
540                 // Do nothing.
541             }
542
543             @Override
544             public void onPageSelected(int position) {
545                 // Set the current WebView.
546                 setCurrentWebView(position);
547
548                 // 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.
549                 if (tabLayout.getSelectedTabPosition() != position) {
550                     // Get a handle for the corresponding tab.
551                     TabLayout.Tab correspondingTab = tabLayout.getTabAt(position);
552
553                     // Assert that the corresponding tab is not null.
554                     assert correspondingTab != null;
555
556                     // Select the corresponding tab.
557                     correspondingTab.select();
558                 }
559             }
560
561             @Override
562             public void onPageScrollStateChanged(int state) {
563                 // Do nothing.
564             }
565         });
566
567         // Display the View SSL Certificate dialog when the currently selected tab is reselected.
568         tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
569             @Override
570             public void onTabSelected(TabLayout.Tab tab) {
571                 // Select the same page in the view pager.
572                 webViewPager.setCurrentItem(tab.getPosition());
573             }
574
575             @Override
576             public void onTabUnselected(TabLayout.Tab tab) {
577                 // Do nothing.
578             }
579
580             @Override
581             public void onTabReselected(TabLayout.Tab tab) {
582                 // Instantiate the View SSL Certificate dialog.
583                 DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
584
585                 // Display the View SSL Certificate dialog.
586                 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
587             }
588         });
589
590         // Add the first tab.
591         addTab(null);
592
593         // Set the bookmarks drawer resources according to the theme.  This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
594         // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
595         if (darkTheme) {
596             launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
597             createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
598             createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
599             bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
600         } else {
601             launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
602             createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
603             createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
604             bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
605         }
606
607         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
608         launchBookmarksActivityFab.setOnClickListener(v -> {
609             // Get a copy of the favorite icon bitmap.
610             Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
611
612             // Create a favorite icon byte array output stream.
613             ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
614
615             // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
616             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
617
618             // Convert the favorite icon byte array stream to a byte array.
619             byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
620
621             // Create an intent to launch the bookmarks activity.
622             Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
623
624             // Add the extra information to the intent.
625             bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
626             bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
627             bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
628             bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
629
630             // Make it so.
631             startActivity(bookmarksIntent);
632         });
633
634         // Set the create new bookmark folder FAB to display an alert dialog.
635         createBookmarkFolderFab.setOnClickListener(v -> {
636             // Create a create bookmark folder dialog.
637             DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
638
639             // Show the create bookmark folder dialog.
640             createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
641         });
642
643         // Set the create new bookmark FAB to display an alert dialog.
644         createBookmarkFab.setOnClickListener(view -> {
645             // Instantiate the create bookmark dialog.
646             DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
647
648             // Display the create bookmark dialog.
649             createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
650         });
651
652         // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
653         findOnPageEditText.addTextChangedListener(new TextWatcher() {
654             @Override
655             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
656                 // Do nothing.
657             }
658
659             @Override
660             public void onTextChanged(CharSequence s, int start, int before, int count) {
661                 // Do nothing.
662             }
663
664             @Override
665             public void afterTextChanged(Editable s) {
666                 // Search for the text in `mainWebView`.
667                 currentWebView.findAllAsync(findOnPageEditText.getText().toString());
668             }
669         });
670
671         // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
672         findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
673             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
674                 // Hide the soft keyboard.
675                 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
676
677                 // Consume the event.
678                 return true;
679             } else {  // A different key was pressed.
680                 // Do not consume the event.
681                 return false;
682             }
683         });
684
685         // Implement swipe to refresh.
686         swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
687
688         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
689         swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayout.getProgressViewStartOffset() - 10, swipeRefreshLayout.getProgressViewEndOffset());
690
691         // Set the swipe to refresh color according to the theme.
692         if (darkTheme) {
693             swipeRefreshLayout.setColorSchemeResources(R.color.blue_800);
694             swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
695         } else {
696             swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
697         }
698
699         // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
700         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
701         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
702
703         // Initialize the bookmarks database helper.  The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
704         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
705
706         // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
707         currentBookmarksFolder = "";
708
709         // Load the home folder, which is `""` in the database.
710         loadBookmarksFolder();
711
712         bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
713             // Convert the id from long to int to match the format of the bookmarks database.
714             int databaseID = (int) id;
715
716             // Get the bookmark cursor for this ID and move it to the first row.
717             Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
718             bookmarkCursor.moveToFirst();
719
720             // Act upon the bookmark according to the type.
721             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
722                 // Store the new folder name in `currentBookmarksFolder`.
723                 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
724
725                 // Load the new folder.
726                 loadBookmarksFolder();
727             } else {  // The selected bookmark is not a folder.
728                 // Load the bookmark URL.
729                 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
730
731                 // Close the bookmarks drawer.
732                 drawerLayout.closeDrawer(GravityCompat.END);
733             }
734
735             // Close the `Cursor`.
736             bookmarkCursor.close();
737         });
738
739         bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
740             // Convert the database ID from `long` to `int`.
741             int databaseId = (int) id;
742
743             // Find out if the selected bookmark is a folder.
744             boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
745
746             if (isFolder) {
747                 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
748                 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
749
750                 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
751                 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
752                 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
753             } else {
754                 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
755                 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
756                 editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark));
757             }
758
759             // Consume the event.
760             return true;
761         });
762
763         // Get the status bar pixel size.
764         int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
765         int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
766
767         // Get the resource density.
768         float screenDensity = getResources().getDisplayMetrics().density;
769
770         // Calculate the drawer header padding.  This is used to move the text in the drawer headers below any cutouts.
771         drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
772         drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
773         drawerHeaderPaddingBottom = (int) (8 * screenDensity);
774
775         // The drawer listener is used to update the navigation menu.`
776         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
777             @Override
778             public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
779             }
780
781             @Override
782             public void onDrawerOpened(@NonNull View drawerView) {
783             }
784
785             @Override
786             public void onDrawerClosed(@NonNull View drawerView) {
787             }
788
789             @Override
790             public void onDrawerStateChanged(int newState) {
791                 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) {  // A drawer is opening or closing.
792                     // Get handles for the drawer headers.
793                     TextView navigationHeaderTextView = findViewById(R.id.navigationText);
794                     TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
795
796                     // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
797                     if (navigationHeaderTextView != null) {
798                         navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
799                     }
800
801                     // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started).  This moves the text in the header below any cutouts.
802                     if (bookmarksHeaderTextView != null) {
803                         bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
804                     }
805
806                     // Update the navigation menu items.
807                     navigationCloseTabMenuItem.setEnabled(tabLayout.getTabCount() > 1);
808                     navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
809                     navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
810                     navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
811                     navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
812
813                     // Hide the keyboard (if displayed).
814                     inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
815
816                     // Clear the focus from from the URL text box and the WebView.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
817                     urlEditText.clearFocus();
818                     currentWebView.clearFocus();
819                 }
820             }
821         });
822
823         // Create the hamburger icon at the start of the AppBar.
824         actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
825
826         // Replace the header that `WebView` creates for `X-Requested-With` with a null value.  The default value is the application ID (com.stoutner.privacybrowser.standard).
827         customHeaders.put("X-Requested-With", "");
828
829         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
830         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
831
832         // Store the application's private data directory.
833         privateDataDirectoryString = getApplicationInfo().dataDir;
834         // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
835
836         // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
837         inFullScreenBrowsingMode = false;
838
839         // Initialize the privacy settings variables.
840         firstPartyCookiesEnabled = false;
841
842         // Inflate a bare WebView to get the default user agent.  It is not used to render content on the screen.
843         @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
844
845         // Get a handle for the WebView.
846         WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
847
848         // Store the default user agent.
849         webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
850
851         // Destroy the bare WebView.
852         bareWebView.destroy();
853     }
854
855     @Override
856     protected void onNewIntent(Intent intent) {
857         // Get the information from the intent.
858         String intentAction = intent.getAction();
859         Uri intentUriData = intent.getData();
860
861         // Only process the URI if it contains data.  If the user pressed the desktop icon after the app was already running the URI will be null.
862         if (intentUriData != null) {
863             // Sets the new intent as the activity intent, which replaces the one that originally started the app.
864             setIntent(intent);
865
866             // Add a new tab.
867             addTab(null);
868
869             // Create a URL string.
870             String url;
871
872             // If the intent action is a web search, perform the search.
873             if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
874                 // Create an encoded URL string.
875                 String encodedUrlString;
876
877                 // Sanitize the search input and convert it to a search.
878                 try {
879                     encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
880                 } catch (UnsupportedEncodingException exception) {
881                     encodedUrlString = "";
882                 }
883
884                 // Add the base search URL.
885                 url = searchURL + encodedUrlString;
886             } else {  // The intent should contain a URL.
887                 // Set the intent data as the URL.
888                 url = intentUriData.toString();
889             }
890
891             // Load the URL.
892             loadUrl(url);
893
894             // Get a handle for the drawer layout.
895             DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
896
897             // Close the navigation drawer if it is open.
898             if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
899                 drawerLayout.closeDrawer(GravityCompat.START);
900             }
901
902             // Close the bookmarks drawer if it is open.
903             if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
904                 drawerLayout.closeDrawer(GravityCompat.END);
905             }
906
907             // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
908             currentWebView.requestFocus();
909         }
910     }
911
912     @Override
913     public void onRestart() {
914         // Run the default commands.
915         super.onRestart();
916
917         // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
918         if (proxyThroughOrbot) {
919             // Request Orbot to start.  If Orbot is already running no hard will be caused by this request.
920             Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
921
922             // Send the intent to the Orbot package.
923             orbotIntent.setPackage("org.torproject.android");
924
925             // Make it so.
926             sendBroadcast(orbotIntent);
927         }
928
929         // Apply the app settings if returning from the Settings activity.
930         if (reapplyAppSettingsOnRestart) {
931             // Apply the app settings.
932             applyAppSettings();
933
934             // Reload the webpage to handle changes to night mode and displaying of images.
935             if (reloadOnRestart) {
936                 // Reload the WebViews.
937                 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
938                     // Get the WebView tab fragment.
939                     WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
940
941                     // Get the fragment view.
942                     View fragmentView = webViewTabFragment.getView();
943
944                     // Only reload the WebViews if they exist.
945                     if (fragmentView != null) {
946                         // Get the nested scroll WebView from the tab fragment.
947                         NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
948
949                         // Reload the WebView.  This doesn't seem to work if for WebViews that aren't visible.
950                         nestedScrollWebView.reload();
951                     }
952                 }
953
954                 // Reset `reloadOnRestartBoolean`.
955                 reloadOnRestart = false;
956             }
957
958             // Reset the return from settings flag.
959             reapplyAppSettingsOnRestart = false;
960         }
961
962         // TODO apply to all the tabs.
963         // Apply the domain settings if returning from the Domains activity.
964         if (reapplyDomainSettingsOnRestart) {
965             // Reset the current domain name so the domain settings will be reapplied.
966             currentWebView.resetCurrentDomainName();
967
968             // Reapply the domain settings.
969             applyDomainSettings(currentWebView, currentWebView.getUrl(), false, true);  // TODO.
970
971             // Reset the reapply domain settings on restart tracker.
972             reapplyDomainSettingsOnRestart = false;
973         }
974
975         // Load the URL on restart (used when loading a bookmark.
976         if (loadUrlOnRestart) {
977             // Load the specified URL.
978             loadUrl(urlToLoadOnRestart);
979
980             // Reset the load on restart tracker.
981             loadUrlOnRestart = false;
982         }
983
984         // Update the bookmarks drawer if returning from the Bookmarks activity.
985         if (restartFromBookmarksActivity) {
986             // Get a handle for the drawer layout.
987             DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
988
989             // Close the bookmarks drawer.
990             drawerLayout.closeDrawer(GravityCompat.END);
991
992             // Reload the bookmarks drawer.
993             loadBookmarksFolder();
994
995             // Reset `restartFromBookmarksActivity`.
996             restartFromBookmarksActivity = false;
997         }
998
999         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
1000         updatePrivacyIcons(true);
1001     }
1002
1003     // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
1004     @Override
1005     public void onResume() {
1006         // Run the default commands.
1007         super.onResume();
1008
1009         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
1010             // Get the WebView tab fragment.
1011             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
1012
1013             // Get the fragment view.
1014             View fragmentView = webViewTabFragment.getView();
1015
1016             // Only resume the WebViews if they exist (they won't when the app is first created).
1017             if (fragmentView != null) {
1018                 // Get the nested scroll WebView from the tab fragment.
1019                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
1020
1021                 // Resume the nested scroll WebView JavaScript timers.
1022                 nestedScrollWebView.resumeTimers();
1023
1024                 // Resume the nested scroll WebView.
1025                 nestedScrollWebView.onResume();
1026             }
1027         }
1028
1029         // Display a message to the user if waiting for Orbot.
1030         if (waitingForOrbot && !orbotStatus.equals("ON")) {
1031             // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
1032             currentWebView.getSettings().setUseWideViewPort(false);
1033
1034             // Load a waiting page.  `null` specifies no encoding, which defaults to ASCII.
1035             currentWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
1036         }
1037
1038         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
1039             // Get a handle for the root frame layouts.
1040             FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
1041
1042             // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
1043             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1044
1045             /* Hide the system bars.
1046              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1047              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
1048              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1049              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1050              */
1051             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
1052                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1053         } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // Resume the adView for the free flavor.
1054             // Resume the ad.
1055             AdHelper.resumeAd(findViewById(R.id.adview));
1056         }
1057     }
1058
1059     @Override
1060     public void onPause() {
1061         // Run the default commands.
1062         super.onPause();
1063
1064         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
1065             // Get the WebView tab fragment.
1066             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
1067
1068             // Get the fragment view.
1069             View fragmentView = webViewTabFragment.getView();
1070
1071             // Only pause the WebViews if they exist (they won't when the app is first created).
1072             if (fragmentView != null) {
1073                 // Get the nested scroll WebView from the tab fragment.
1074                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
1075
1076                 // Pause the nested scroll WebView.
1077                 nestedScrollWebView.onPause();
1078
1079                 // Pause the nested scroll WebView JavaScript timers.
1080                 nestedScrollWebView.pauseTimers();
1081             }
1082         }
1083
1084         // Pause the ad or it will continue to consume resources in the background on the free flavor.
1085         if (BuildConfig.FLAVOR.contentEquals("free")) {
1086             // Pause the ad.
1087             AdHelper.pauseAd(findViewById(R.id.adview));
1088         }
1089     }
1090
1091     @Override
1092     public void onDestroy() {
1093         // Unregister the Orbot status broadcast receiver.
1094         this.unregisterReceiver(orbotStatusBroadcastReceiver);
1095
1096         // Close the bookmarks cursor and database.
1097         bookmarksCursor.close();
1098         bookmarksDatabaseHelper.close();
1099
1100         // Run the default commands.
1101         super.onDestroy();
1102     }
1103
1104     @Override
1105     public boolean onCreateOptionsMenu(Menu menu) {
1106         // Inflate the menu.  This adds items to the action bar if it is present.
1107         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
1108
1109         // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
1110         optionsMenu = menu;
1111
1112         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
1113         updatePrivacyIcons(false);
1114
1115         // Get handles for the menu items.
1116         MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
1117         MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
1118         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
1119         MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
1120         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
1121         MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
1122         blocklistsMenuItem = menu.findItem(R.id.blocklists);
1123         easyListMenuItem = menu.findItem(R.id.easylist);
1124         easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
1125         fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
1126         fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
1127         ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
1128         blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
1129         MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
1130
1131         // Only display third-party cookies if API >= 21
1132         toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
1133
1134         // Only display the form data menu items if the API < 26.
1135         toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
1136         clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
1137
1138         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
1139         clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
1140
1141         // Only show Ad Consent if this is the free flavor.
1142         adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
1143
1144         // Get the shared preference values.
1145         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1146
1147         // Get the status of the additional AppBar icons.
1148         boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
1149
1150         // 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.
1151         if (displayAdditionalAppBarIcons) {
1152             toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1153             toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1154             refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
1155         } else { //Do not display the additional icons.
1156             toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1157             toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1158             refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1159         }
1160
1161         // Replace Refresh with Stop if a URL is already loading.
1162         if (currentWebView != null && currentWebView.getProgress() != 100) {
1163             // Set the title.
1164             refreshMenuItem.setTitle(R.string.stop);
1165
1166             // If the icon is displayed in the AppBar, set it according to the theme.
1167             if (displayAdditionalAppBarIcons) {
1168                 if (darkTheme) {
1169                     refreshMenuItem.setIcon(R.drawable.close_dark);
1170                 } else {
1171                     refreshMenuItem.setIcon(R.drawable.close_light);
1172                 }
1173             }
1174         }
1175
1176         return true;
1177     }
1178
1179     @Override
1180     public boolean onPrepareOptionsMenu(Menu menu) {
1181         // Get handles for the menu items.
1182         MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
1183         MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
1184         MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
1185         MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
1186         MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
1187         MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
1188         MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
1189         MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
1190         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
1191         MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
1192         MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
1193         MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
1194         MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
1195         MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
1196
1197         // Get a handle for the cookie manager.
1198         CookieManager cookieManager = CookieManager.getInstance();
1199
1200         // Initialize the current user agent string and the font size.
1201         String currentUserAgent = getString(R.string.user_agent_privacy_browser);
1202         int fontSize = 100;
1203
1204         // 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.
1205         if (currentWebView != null) {
1206             // Set the add or edit domain text.
1207             if (currentWebView.getDomainSettingsApplied()) {
1208                 addOrEditDomain.setTitle(R.string.edit_domain_settings);
1209             } else {
1210                 addOrEditDomain.setTitle(R.string.add_domain_settings);
1211             }
1212
1213             // Get the current user agent from the WebView.
1214             currentUserAgent = currentWebView.getSettings().getUserAgentString();
1215
1216             // Get the current font size from the
1217             fontSize = currentWebView.getSettings().getTextZoom();
1218
1219             // Set the status of the menu item checkboxes.
1220             domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1221             saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
1222             easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1223             easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1224             fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1225             fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1226             ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1227             blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1228             swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
1229             displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
1230             nightModeMenuItem.setChecked(currentWebView.getNightMode());
1231
1232             // Initialize the display names for the blocklists with the number of blocked requests.
1233             blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
1234             easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
1235             easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
1236             fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
1237             fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
1238             ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
1239             blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
1240
1241             // Only modify third-party cookies if the API >= 21.
1242             if (Build.VERSION.SDK_INT >= 21) {
1243                 // Set the status of the third-party cookies checkbox.
1244                 thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1245
1246                 // Enable third-party cookies if first-party cookies are enabled.
1247                 thirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
1248             }
1249
1250             // Enable DOM Storage if JavaScript is enabled.
1251             domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
1252         }
1253
1254         // Set the status of the menu item checkboxes.
1255         firstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
1256         proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
1257
1258         // Enable Clear Cookies if there are any.
1259         clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
1260
1261         // Get a count of the number of files in the Local Storage directory.
1262         File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
1263         int localStorageDirectoryNumberOfFiles = 0;
1264         if (localStorageDirectory.exists()) {
1265             localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
1266         }
1267
1268         // Get a count of the number of files in the IndexedDB directory.
1269         File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
1270         int indexedDBDirectoryNumberOfFiles = 0;
1271         if (indexedDBDirectory.exists()) {
1272             indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
1273         }
1274
1275         // Enable Clear DOM Storage if there is any.
1276         clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
1277
1278         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
1279         if (Build.VERSION.SDK_INT < 26) {
1280             // Get the WebView database.
1281             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1282
1283             // Enable the clear form data menu item if there is anything to clear.
1284             clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
1285         }
1286
1287         // Enable Clear Data if any of the submenu items are enabled.
1288         clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
1289
1290         // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
1291         fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
1292
1293         // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
1294         if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
1295             menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
1296         } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
1297             menu.findItem(R.id.user_agent_webview_default).setChecked(true);
1298         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
1299             menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
1300         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
1301             menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
1302         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
1303             menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
1304         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
1305             menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
1306         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
1307             menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
1308         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
1309             menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
1310         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
1311             menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
1312         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
1313             menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
1314         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
1315             menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
1316         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
1317             menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
1318         } else {  // Custom user agent.
1319             menu.findItem(R.id.user_agent_custom).setChecked(true);
1320         }
1321
1322         // Instantiate the font size title and the selected font size menu item.
1323         String fontSizeTitle;
1324         MenuItem selectedFontSizeMenuItem;
1325
1326         // Prepare the font size title and current size menu item.
1327         switch (fontSize) {
1328             case 25:
1329                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
1330                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
1331                 break;
1332
1333             case 50:
1334                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
1335                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
1336                 break;
1337
1338             case 75:
1339                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
1340                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
1341                 break;
1342
1343             case 100:
1344                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
1345                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
1346                 break;
1347
1348             case 125:
1349                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
1350                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
1351                 break;
1352
1353             case 150:
1354                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
1355                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
1356                 break;
1357
1358             case 175:
1359                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
1360                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
1361                 break;
1362
1363             case 200:
1364                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
1365                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
1366                 break;
1367
1368             default:
1369                 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
1370                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
1371                 break;
1372         }
1373
1374         // Set the font size title and select the current size menu item.
1375         fontSizeMenuItem.setTitle(fontSizeTitle);
1376         selectedFontSizeMenuItem.setChecked(true);
1377
1378         // Run all the other default commands.
1379         super.onPrepareOptionsMenu(menu);
1380
1381         // Display the menu.
1382         return true;
1383     }
1384
1385     @Override
1386     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
1387     @SuppressLint("SetJavaScriptEnabled")
1388     // removeAllCookies is deprecated, but it is required for API < 21.
1389     @SuppressWarnings("deprecation")
1390     public boolean onOptionsItemSelected(MenuItem menuItem) {
1391         // Reenter full screen browsing mode if it was interrupted by the options menu.  <https://redmine.stoutner.com/issues/389>
1392         if (inFullScreenBrowsingMode) {
1393             // Remove the translucent status flag.  This is necessary so the root frame layout can fill the entire screen.
1394             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1395
1396             FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
1397
1398             /* Hide the system bars.
1399              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1400              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
1401              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1402              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1403              */
1404             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
1405                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1406         }
1407
1408         // Get the selected menu item ID.
1409         int menuItemId = menuItem.getItemId();
1410
1411         // Get a handle for the shared preferences.
1412         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1413
1414         // Get a handle for the cookie manager.
1415         CookieManager cookieManager = CookieManager.getInstance();
1416
1417         // Run the commands that correlate to the selected menu item.
1418         switch (menuItemId) {
1419             case R.id.toggle_javascript:
1420                 // Toggle the JavaScript status.
1421                 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1422
1423                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1424                 updatePrivacyIcons(true);
1425
1426                 // Display a `Snackbar`.
1427                 if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
1428                     Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1429                 } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
1430                     Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1431                 } else {  // Privacy mode.
1432                     Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1433                 }
1434
1435                 // Reload the current WebView.
1436                 currentWebView.reload();
1437                 return true;
1438
1439             case R.id.add_or_edit_domain:
1440                 if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
1441                     // Reapply the domain settings on returning to `MainWebViewActivity`.
1442                     reapplyDomainSettingsOnRestart = true;
1443
1444                     // TODO.  Move these to `putExtra`.  The certificate can be stored as strings.
1445                     // Store the current SSL certificate and IP addresses in the domains activity.
1446                     DomainsActivity.currentSslCertificate = currentWebView.getCertificate();
1447                     DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses();
1448
1449                     // Create an intent to launch the domains activity.
1450                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
1451
1452                     // Add the extra information to the intent.
1453                     domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1454                     domainsIntent.putExtra("close_on_back", true);
1455                     domainsIntent.putExtra("current_url", currentWebView.getUrl());
1456
1457                     // Make it so.
1458                     startActivity(domainsIntent);
1459                 } else {  // Add a new domain.
1460                     // Apply the new domain settings on returning to `MainWebViewActivity`.
1461                     reapplyDomainSettingsOnRestart = true;
1462
1463                     // Get the current domain
1464                     Uri currentUri = Uri.parse(currentWebView.getUrl());
1465                     String currentDomain = currentUri.getHost();
1466
1467                     // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1468                     DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1469
1470                     // Create the domain and store the database ID.
1471                     int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1472
1473                     // TODO.  Move these to `putExtra`.  The certificate can be stored as strings.
1474                     // Store the current SSL certificate and IP addresses in the domains activity.
1475                     DomainsActivity.currentSslCertificate = currentWebView.getCertificate();
1476                     DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses();
1477
1478                     // Create an intent to launch the domains activity.
1479                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
1480
1481                     // Add the extra information to the intent.
1482                     domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1483                     domainsIntent.putExtra("close_on_back", true);
1484                     domainsIntent.putExtra("current_url", currentWebView.getUrl());
1485
1486                     // Make it so.
1487                     startActivity(domainsIntent);
1488                 }
1489                 return true;
1490
1491             case R.id.toggle_first_party_cookies:
1492                 // Switch the status of firstPartyCookiesEnabled.
1493                 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
1494
1495                 // Update the menu checkbox.
1496                 menuItem.setChecked(firstPartyCookiesEnabled);
1497
1498                 // Apply the new cookie status.
1499                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1500
1501                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1502                 updatePrivacyIcons(true);
1503
1504                 // Display a `Snackbar`.
1505                 if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
1506                     Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1507                 } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
1508                     Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1509                 } else {  // Privacy mode.
1510                     Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1511                 }
1512
1513                 // Reload the current WebView.
1514                 currentWebView.reload();
1515                 return true;
1516
1517             case R.id.toggle_third_party_cookies:
1518                 if (Build.VERSION.SDK_INT >= 21) {
1519                     // Switch the status of thirdPartyCookiesEnabled.
1520                     cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
1521
1522                     // Update the menu checkbox.
1523                     menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1524
1525                     // Display a snackbar.
1526                     if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
1527                         Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1528                     } else {
1529                         Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1530                     }
1531
1532                     // Reload the current WebView.
1533                     currentWebView.reload();
1534                 } // Else do nothing because SDK < 21.
1535                 return true;
1536
1537             case R.id.toggle_dom_storage:
1538                 // Toggle the status of domStorageEnabled.
1539                 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1540
1541                 // Update the menu checkbox.
1542                 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1543
1544                 // Update the privacy icon.  `true` refreshes the app bar icons.
1545                 updatePrivacyIcons(true);
1546
1547                 // Display a snackbar.
1548                 if (currentWebView.getSettings().getDomStorageEnabled()) {
1549                     Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1550                 } else {
1551                     Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1552                 }
1553
1554                 // Reload the current WebView.
1555                 currentWebView.reload();
1556                 return true;
1557
1558             // Form data can be removed once the minimum API >= 26.
1559             case R.id.toggle_save_form_data:
1560                 // Switch the status of saveFormDataEnabled.
1561                 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1562
1563                 // Update the menu checkbox.
1564                 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1565
1566                 // Display a snackbar.
1567                 if (currentWebView.getSettings().getSaveFormData()) {
1568                     Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1569                 } else {
1570                     Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1571                 }
1572
1573                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1574                 updatePrivacyIcons(true);
1575
1576                 // Reload the current WebView.
1577                 currentWebView.reload();
1578                 return true;
1579
1580             case R.id.clear_cookies:
1581                 Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1582                         .setAction(R.string.undo, v -> {
1583                             // Do nothing because everything will be handled by `onDismissed()` below.
1584                         })
1585                         .addCallback(new Snackbar.Callback() {
1586                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1587                             @Override
1588                             public void onDismissed(Snackbar snackbar, int event) {
1589                                 switch (event) {
1590                                     // The user pushed the undo button.
1591                                     case Snackbar.Callback.DISMISS_EVENT_ACTION:
1592                                         // Do nothing.
1593                                         break;
1594
1595                                     // The snackbar was dismissed without the undo button being pushed.
1596                                     default:
1597                                         // `cookieManager.removeAllCookie()` varies by SDK.
1598                                         if (Build.VERSION.SDK_INT < 21) {
1599                                             cookieManager.removeAllCookie();
1600                                         } else {
1601                                             cookieManager.removeAllCookies(null);
1602                                         }
1603                                 }
1604                             }
1605                         })
1606                         .show();
1607                 return true;
1608
1609             case R.id.clear_dom_storage:
1610                 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1611                         .setAction(R.string.undo, v -> {
1612                             // Do nothing because everything will be handled by `onDismissed()` below.
1613                         })
1614                         .addCallback(new Snackbar.Callback() {
1615                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1616                             @Override
1617                             public void onDismissed(Snackbar snackbar, int event) {
1618                                 switch (event) {
1619                                     // The user pushed the undo button.
1620                                     case Snackbar.Callback.DISMISS_EVENT_ACTION:
1621                                         // Do nothing.
1622                                         break;
1623
1624                                     // The snackbar was dismissed without the undo button being pushed.
1625                                     default:
1626                                         // Delete the DOM Storage.
1627                                         WebStorage webStorage = WebStorage.getInstance();
1628                                         webStorage.deleteAllData();
1629
1630                                         // Initialize a handler to manually delete the DOM storage files and directories.
1631                                         Handler deleteDomStorageHandler = new Handler();
1632
1633                                         // Setup a runnable to manually delete the DOM storage files and directories.
1634                                         Runnable deleteDomStorageRunnable = () -> {
1635                                             try {
1636                                                 // Get a handle for the runtime.
1637                                                 Runtime runtime = Runtime.getRuntime();
1638
1639                                                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1640                                                 Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1641
1642                                                 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1643                                                 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1644                                                 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1645                                                 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1646                                                 Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1647
1648                                                 // Wait for the processes to finish.
1649                                                 deleteLocalStorageProcess.waitFor();
1650                                                 deleteIndexProcess.waitFor();
1651                                                 deleteQuotaManagerProcess.waitFor();
1652                                                 deleteQuotaManagerJournalProcess.waitFor();
1653                                                 deleteDatabasesProcess.waitFor();
1654                                             } catch (Exception exception) {
1655                                                 // Do nothing if an error is thrown.
1656                                             }
1657                                         };
1658
1659                                         // Manually delete the DOM storage files after 200 milliseconds.
1660                                         deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1661                                 }
1662                             }
1663                         })
1664                         .show();
1665                 return true;
1666
1667             // Form data can be remove once the minimum API >= 26.
1668             case R.id.clear_form_data:
1669                 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1670                         .setAction(R.string.undo, v -> {
1671                             // Do nothing because everything will be handled by `onDismissed()` below.
1672                         })
1673                         .addCallback(new Snackbar.Callback() {
1674                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1675                             @Override
1676                             public void onDismissed(Snackbar snackbar, int event) {
1677                                 switch (event) {
1678                                     // The user pushed the undo button.
1679                                     case Snackbar.Callback.DISMISS_EVENT_ACTION:
1680                                         // Do nothing.
1681                                         break;
1682
1683                                     // The snackbar was dismissed without the `Undo` button being pushed.
1684                                     default:
1685                                         // Delete the form data.
1686                                         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1687                                         mainWebViewDatabase.clearFormData();
1688                                 }
1689                             }
1690                         })
1691                         .show();
1692                 return true;
1693
1694             case R.id.easylist:
1695                 // Toggle the EasyList status.
1696                 currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1697
1698                 // Update the menu checkbox.
1699                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1700
1701                 // Reload the current WebView.
1702                 currentWebView.reload();
1703                 return true;
1704
1705             case R.id.easyprivacy:
1706                 // Toggle the EasyPrivacy status.
1707                 currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1708
1709                 // Update the menu checkbox.
1710                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1711
1712                 // Reload the current WebView.
1713                 currentWebView.reload();
1714                 return true;
1715
1716             case R.id.fanboys_annoyance_list:
1717                 // Toggle Fanboy's Annoyance List status.
1718                 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1719
1720                 // Update the menu checkbox.
1721                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1722
1723                 // Update the staus of Fanboy's Social Blocking List.
1724                 MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
1725                 fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1726
1727                 // Reload the current WebView.
1728                 currentWebView.reload();
1729                 return true;
1730
1731             case R.id.fanboys_social_blocking_list:
1732                 // Toggle Fanboy's Social Blocking List status.
1733                 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1734
1735                 // Update the menu checkbox.
1736                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1737
1738                 // Reload the current WebView.
1739                 currentWebView.reload();
1740                 return true;
1741
1742             case R.id.ultraprivacy:
1743                 // Toggle the UltraPrivacy status.
1744                 currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1745
1746                 // Update the menu checkbox.
1747                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1748
1749                 // Reload the current WebView.
1750                 currentWebView.reload();
1751                 return true;
1752
1753             case R.id.block_all_third_party_requests:
1754                 //Toggle the third-party requests blocker status.
1755                 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1756
1757                 // Update the menu checkbox.
1758                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1759
1760                 // Reload the current WebView.
1761                 currentWebView.reload();
1762                 return true;
1763
1764             case R.id.user_agent_privacy_browser:
1765                 // Update the user agent.
1766                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1767
1768                 // Reload the current WebView.
1769                 currentWebView.reload();
1770                 return true;
1771
1772             case R.id.user_agent_webview_default:
1773                 // Update the user agent.
1774                 currentWebView.getSettings().setUserAgentString("");
1775
1776                 // Reload the current WebView.
1777                 currentWebView.reload();
1778                 return true;
1779
1780             case R.id.user_agent_firefox_on_android:
1781                 // Update the user agent.
1782                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1783
1784                 // Reload the current WebView.
1785                 currentWebView.reload();
1786                 return true;
1787
1788             case R.id.user_agent_chrome_on_android:
1789                 // Update the user agent.
1790                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1791
1792                 // Reload the current WebView.
1793                 currentWebView.reload();
1794                 return true;
1795
1796             case R.id.user_agent_safari_on_ios:
1797                 // Update the user agent.
1798                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1799
1800                 // Reload the current WebView.
1801                 currentWebView.reload();
1802                 return true;
1803
1804             case R.id.user_agent_firefox_on_linux:
1805                 // Update the user agent.
1806                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1807
1808                 // Reload the current WebView.
1809                 currentWebView.reload();
1810                 return true;
1811
1812             case R.id.user_agent_chromium_on_linux:
1813                 // Update the user agent.
1814                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1815
1816                 // Reload the current WebView.
1817                 currentWebView.reload();
1818                 return true;
1819
1820             case R.id.user_agent_firefox_on_windows:
1821                 // Update the user agent.
1822                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1823
1824                 // Reload the current WebView.
1825                 currentWebView.reload();
1826                 return true;
1827
1828             case R.id.user_agent_chrome_on_windows:
1829                 // Update the user agent.
1830                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1831
1832                 // Reload the current WebView.
1833                 currentWebView.reload();
1834                 return true;
1835
1836             case R.id.user_agent_edge_on_windows:
1837                 // Update the user agent.
1838                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1839
1840                 // Reload the current WebView.
1841                 currentWebView.reload();
1842                 return true;
1843
1844             case R.id.user_agent_internet_explorer_on_windows:
1845                 // Update the user agent.
1846                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1847
1848                 // Reload the current WebView.
1849                 currentWebView.reload();
1850                 return true;
1851
1852             case R.id.user_agent_safari_on_macos:
1853                 // Update the user agent.
1854                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1855
1856                 // Reload the current WebView.
1857                 currentWebView.reload();
1858                 return true;
1859
1860             case R.id.user_agent_custom:
1861                 // Update the user agent.
1862                 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1863
1864                 // Reload the current WebView.
1865                 currentWebView.reload();
1866                 return true;
1867
1868             case R.id.font_size_twenty_five_percent:
1869                 currentWebView.getSettings().setTextZoom(25);
1870                 return true;
1871
1872             case R.id.font_size_fifty_percent:
1873                 currentWebView.getSettings().setTextZoom(50);
1874                 return true;
1875
1876             case R.id.font_size_seventy_five_percent:
1877                 currentWebView.getSettings().setTextZoom(75);
1878                 return true;
1879
1880             case R.id.font_size_one_hundred_percent:
1881                 currentWebView.getSettings().setTextZoom(100);
1882                 return true;
1883
1884             case R.id.font_size_one_hundred_twenty_five_percent:
1885                 currentWebView.getSettings().setTextZoom(125);
1886                 return true;
1887
1888             case R.id.font_size_one_hundred_fifty_percent:
1889                 currentWebView.getSettings().setTextZoom(150);
1890                 return true;
1891
1892             case R.id.font_size_one_hundred_seventy_five_percent:
1893                 currentWebView.getSettings().setTextZoom(175);
1894                 return true;
1895
1896             case R.id.font_size_two_hundred_percent:
1897                 currentWebView.getSettings().setTextZoom(200);
1898                 return true;
1899
1900             case R.id.swipe_to_refresh:
1901                 // Toggle the stored status of swipe to refresh.
1902                 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1903
1904                 // Get a handle for the swipe refresh layout.
1905                 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
1906
1907                 // Update the swipe refresh layout.
1908                 if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
1909                     if (Build.VERSION.SDK_INT >= 23) {  // For API >= 23, the status of the scroll refresh listener is continuously updated by the on scroll change listener.
1910                         // Only enable the swipe refresh layout if the WebView is scrolled to the top.
1911                         swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
1912                     } else {  // For API < 23, the swipe refresh layout is always enabled.
1913                         // Enable the swipe refresh layout.
1914                         swipeRefreshLayout.setEnabled(true);
1915                     }
1916                 } else {  // Swipe to refresh is disabled.
1917                     // Disable the swipe refresh layout.
1918                     swipeRefreshLayout.setEnabled(false);
1919                 }
1920                 return true;
1921
1922             case R.id.display_images:
1923                 if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
1924                     // Disable loading of images.
1925                     currentWebView.getSettings().setLoadsImagesAutomatically(false);
1926
1927                     // Reload the website to remove existing images.
1928                     currentWebView.reload();
1929                 } else {  // Images are not currently loaded automatically.
1930                     // Enable loading of images.  Missing images will be loaded without the need for a reload.
1931                     currentWebView.getSettings().setLoadsImagesAutomatically(true);
1932                 }
1933                 return true;
1934
1935             case R.id.night_mode:
1936                 // Toggle night mode.
1937                 currentWebView.setNightMode(!currentWebView.getNightMode());
1938
1939                 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
1940                 if (currentWebView.getNightMode()) {  // Night mode is enabled, which requires JavaScript.
1941                     // Enable JavaScript.
1942                     currentWebView.getSettings().setJavaScriptEnabled(true);
1943                 } else if (currentWebView.getDomainSettingsApplied()) {  // Night mode is disabled and domain settings are applied.  Set JavaScript according to the domain settings.
1944                     // Apply the JavaScript preference that was stored the last time domain settings were loaded.
1945                     currentWebView.getSettings().setJavaScriptEnabled(domainSettingsJavaScriptEnabled);  // TODO.
1946                 } else {  // Night mode is disabled and domain settings are not applied.  Set JavaScript according to the global preference.
1947                     // Apply the JavaScript preference.
1948                     currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
1949                 }
1950
1951                 // Update the privacy icons.
1952                 updatePrivacyIcons(false);
1953
1954                 // Reload the website.
1955                 currentWebView.reload();
1956                 return true;
1957
1958             case R.id.find_on_page:
1959                 // Get a handle for the views.
1960                 Toolbar toolbar = findViewById(R.id.toolbar);
1961                 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1962                 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1963
1964                 // Hide the toolbar.
1965                 toolbar.setVisibility(View.GONE);
1966
1967                 // Show the find on page linear layout.
1968                 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1969
1970                 // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
1971                 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1972                 findOnPageEditText.postDelayed(() -> {
1973                     // Set the focus on `findOnPageEditText`.
1974                     findOnPageEditText.requestFocus();
1975
1976                     // Get a handle for the input method manager.
1977                     InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1978
1979                     // Remove the lint warning below that the input method manager might be null.
1980                     assert inputMethodManager != null;
1981
1982                     // Display the keyboard.  `0` sets no input flags.
1983                     inputMethodManager.showSoftInput(findOnPageEditText, 0);
1984                 }, 200);
1985                 return true;
1986
1987             case R.id.view_source:
1988                 // Create an intent to launch the view source activity.
1989                 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1990
1991                 // Add the variables to the intent.
1992                 viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
1993                 viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
1994
1995                 // Make it so.
1996                 startActivity(viewSourceIntent);
1997                 return true;
1998
1999             case R.id.share_url:
2000                 // Setup the share string.
2001                 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
2002
2003                 // Create the share intent.
2004                 Intent shareIntent = new Intent(Intent.ACTION_SEND);
2005                 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
2006                 shareIntent.setType("text/plain");
2007
2008                 // Make it so.
2009                 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
2010                 return true;
2011
2012             case R.id.print:
2013                 // Get a `PrintManager` instance.
2014                 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
2015
2016                 // Create a print document adapter form the current WebView.
2017                 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
2018
2019                 // Remove the lint error below that `printManager` might be `null`.
2020                 assert printManager != null;
2021
2022                 // Print the document.  The print attributes are `null`.
2023                 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
2024                 return true;
2025
2026             case R.id.open_with_app:
2027                 openWithApp(currentWebView.getUrl());
2028                 return true;
2029
2030             case R.id.open_with_browser:
2031                 openWithBrowser(currentWebView.getUrl());
2032                 return true;
2033
2034             case R.id.add_to_homescreen:
2035                 // Instantiate the create home screen shortcut dialog.
2036                 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
2037                         currentWebView.getFavoriteOrDefaultIcon());
2038
2039                 // Show the create home screen shortcut dialog.
2040                 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
2041                 return true;
2042
2043             case R.id.proxy_through_orbot:
2044                 // Toggle the proxy through Orbot variable.
2045                 proxyThroughOrbot = !proxyThroughOrbot;
2046
2047                 // Apply the proxy through Orbot settings.
2048                 applyProxyThroughOrbot(true);
2049                 return true;
2050
2051             case R.id.refresh:
2052                 if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
2053                     // Reload the current WebView.
2054                     currentWebView.reload();
2055                 } else {  // The stop button was pushed.
2056                     // Stop the loading of the WebView.
2057                     currentWebView.stopLoading();
2058                 }
2059                 return true;
2060
2061             case R.id.ad_consent:
2062                 // Display the ad consent dialog.
2063                 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2064                 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
2065                 return true;
2066
2067             default:
2068                 // Don't consume the event.
2069                 return super.onOptionsItemSelected(menuItem);
2070         }
2071     }
2072
2073     // removeAllCookies is deprecated, but it is required for API < 21.
2074     @SuppressWarnings("deprecation")
2075     @Override
2076     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2077         // Get the menu item ID.
2078         int menuItemId = menuItem.getItemId();
2079
2080         // Run the commands that correspond to the selected menu item.
2081         switch (menuItemId) {
2082             case R.id.close_tab:
2083                 // Get a handle for the tab layout and the view pager.
2084                 TabLayout tabLayout = findViewById(R.id.tablayout);
2085                 ViewPager webViewPager = findViewById(R.id.webviewpager);
2086
2087                 // Get the current tab number.
2088                 int currentTabNumber = tabLayout.getSelectedTabPosition();
2089
2090                 // Delete the current tab.
2091                 tabLayout.removeTabAt(currentTabNumber);
2092
2093                 // Delete the current page.  If the selected page number did not change during the delete, it will return true, meaning that the current WebView must be reset.
2094                 if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
2095                     setCurrentWebView(currentTabNumber);
2096                 }
2097                 break;
2098
2099             case R.id.clear_and_exit:
2100                 // Close the bookmarks cursor and database.
2101                 bookmarksCursor.close();
2102                 bookmarksDatabaseHelper.close();
2103
2104                 // Get a handle for the shared preferences.
2105                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2106
2107                 // Get the status of the clear everything preference.
2108                 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
2109
2110                 // Get a handle for the runtime.
2111                 Runtime runtime = Runtime.getRuntime();
2112
2113                 // Clear cookies.
2114                 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
2115                     // The command to remove cookies changed slightly in API 21.
2116                     if (Build.VERSION.SDK_INT >= 21) {
2117                         CookieManager.getInstance().removeAllCookies(null);
2118                     } else {
2119                         CookieManager.getInstance().removeAllCookie();
2120                     }
2121
2122                     // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
2123                     try {
2124                         // Two commands must be used because `Runtime.exec()` does not like `*`.
2125                         Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
2126                         Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
2127
2128                         // Wait until the processes have finished.
2129                         deleteCookiesProcess.waitFor();
2130                         deleteCookiesJournalProcess.waitFor();
2131                     } catch (Exception exception) {
2132                         // Do nothing if an error is thrown.
2133                     }
2134                 }
2135
2136                 // Clear DOM storage.
2137                 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
2138                     // Ask `WebStorage` to clear the DOM storage.
2139                     WebStorage webStorage = WebStorage.getInstance();
2140                     webStorage.deleteAllData();
2141
2142                     // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
2143                     try {
2144                         // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2145                         Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2146
2147                         // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2148                         Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2149                         Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2150                         Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2151                         Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2152
2153                         // Wait until the processes have finished.
2154                         deleteLocalStorageProcess.waitFor();
2155                         deleteIndexProcess.waitFor();
2156                         deleteQuotaManagerProcess.waitFor();
2157                         deleteQuotaManagerJournalProcess.waitFor();
2158                         deleteDatabaseProcess.waitFor();
2159                     } catch (Exception exception) {
2160                         // Do nothing if an error is thrown.
2161                     }
2162                 }
2163
2164                 // Clear form data if the API < 26.
2165                 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
2166                     WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
2167                     webViewDatabase.clearFormData();
2168
2169                     // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
2170                     try {
2171                         // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
2172                         Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
2173                         Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
2174
2175                         // Wait until the processes have finished.
2176                         deleteWebDataProcess.waitFor();
2177                         deleteWebDataJournalProcess.waitFor();
2178                     } catch (Exception exception) {
2179                         // Do nothing if an error is thrown.
2180                     }
2181                 }
2182
2183                 // Clear the cache.
2184                 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
2185                     // Clear the cache from each WebView.
2186                     for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
2187                         // Get the WebView tab fragment.
2188                         WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
2189
2190                         // Get the fragment view.
2191                         View fragmentView = webViewTabFragment.getView();
2192
2193                         // Only clear the cache if the WebView exists.
2194                         if (fragmentView != null) {
2195                             // Get the nested scroll WebView from the tab fragment.
2196                             NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
2197
2198                             // Clear the cache for this WebView.
2199                             nestedScrollWebView.clearCache(true);
2200                         }
2201                     }
2202
2203                     // Manually delete the cache directories.
2204                     try {
2205                         // Delete the main cache directory.
2206                         Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache");
2207
2208                         // Delete the secondary `Service Worker` cache directory.
2209                         // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2210                         Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
2211
2212                         // Wait until the processes have finished.
2213                         deleteCacheProcess.waitFor();
2214                         deleteServiceWorkerProcess.waitFor();
2215                     } catch (Exception exception) {
2216                         // Do nothing if an error is thrown.
2217                     }
2218                 }
2219
2220                 // Wipe out each WebView.
2221                 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
2222                     // Get the WebView tab fragment.
2223                     WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
2224
2225                     // Get the fragment view.
2226                     View fragmentView = webViewTabFragment.getView();
2227
2228                     // Only wipe out the WebView if it exists.
2229                     if (fragmentView != null) {
2230                         // Get the nested scroll WebView from the tab fragment.
2231                         NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
2232
2233                         // Clear SSL certificate preferences for this WebView.
2234                         nestedScrollWebView.clearSslPreferences();
2235
2236                         // Clear the back/forward history for this WebView.
2237                         nestedScrollWebView.clearHistory();
2238
2239                         // Destroy the internal state of `mainWebView`.
2240                         nestedScrollWebView.destroy();
2241                     }
2242                 }
2243
2244                 // Clear the custom headers.
2245                 customHeaders.clear();
2246
2247                 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
2248                 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
2249                 if (clearEverything) {
2250                     try {
2251                         // Delete the folder.
2252                         Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
2253
2254                         // Wait until the process has finished.
2255                         deleteAppWebviewProcess.waitFor();
2256                     } catch (Exception exception) {
2257                         // Do nothing if an error is thrown.
2258                     }
2259                 }
2260
2261                 // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
2262                 if (Build.VERSION.SDK_INT >= 21) {
2263                     finishAndRemoveTask();
2264                 } else {
2265                     finish();
2266                 }
2267
2268                 // Remove the terminated program from RAM.  The status code is `0`.
2269                 System.exit(0);
2270                 break;
2271
2272             case R.id.home:
2273                 loadUrl(homepage);
2274                 break;
2275
2276             case R.id.back:
2277                 if (currentWebView.canGoBack()) {
2278                     // Reset the current domain name so that navigation works if third-party requests are blocked.
2279                     currentWebView.resetCurrentDomainName();
2280
2281                     // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2282                     navigatingHistory = true;
2283
2284                     // Load the previous website in the history.
2285                     currentWebView.goBack();
2286                 }
2287                 break;
2288
2289             case R.id.forward:
2290                 if (currentWebView.canGoForward()) {
2291                     // Reset the current domain name so that navigation works if third-party requests are blocked.
2292                     currentWebView.resetCurrentDomainName();
2293
2294                     // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
2295                     navigatingHistory = true;
2296
2297                     // Load the next website in the history.
2298                     currentWebView.goForward();
2299                 }
2300                 break;
2301
2302             case R.id.history:
2303                 // Get the `WebBackForwardList`.
2304                 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2305
2306                 // Show the URL history dialog and name this instance `R.string.history`.
2307                 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
2308                 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2309                 break;
2310
2311             case R.id.requests:
2312                 // Populate the resource requests.
2313                 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
2314
2315                 // Create an intent to launch the Requests activity.
2316                 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2317
2318                 // Add the block third-party requests status to the intent.
2319                 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
2320
2321                 // Make it so.
2322                 startActivity(requestsIntent);
2323                 break;
2324
2325             case R.id.downloads:
2326                 // Launch the system Download Manager.
2327                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2328
2329                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
2330                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2331
2332                 startActivity(downloadManagerIntent);
2333                 break;
2334
2335             case R.id.domains:
2336                 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2337                 reapplyDomainSettingsOnRestart = true;
2338
2339                 // TODO.  Move these to `putExtra`.  The certificate can be stored as strings.
2340                 // Store the current SSL certificate and IP addresses in the domains activity.
2341                 DomainsActivity.currentSslCertificate = currentWebView.getCertificate();
2342                 DomainsActivity.currentIpAddresses = currentWebView.getCurrentIpAddresses();
2343
2344                 // Launch the domains activity.
2345                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2346
2347                 // Add the extra information to the intent.
2348                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
2349
2350                 // Make it so.
2351                 startActivity(domainsIntent);
2352                 break;
2353
2354             case R.id.settings:
2355                 // Set the flag to reapply app settings on restart when returning from Settings.
2356                 reapplyAppSettingsOnRestart = true;
2357
2358                 // Set the flag to reapply the domain settings on restart when returning from Settings.
2359                 reapplyDomainSettingsOnRestart = true;
2360
2361                 // Launch the settings activity.
2362                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
2363                 startActivity(settingsIntent);
2364                 break;
2365
2366             case R.id.import_export:
2367                 // Launch the import/export activity.
2368                 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
2369                 startActivity(importExportIntent);
2370                 break;
2371
2372             case R.id.logcat:
2373                 // Launch the logcat activity.
2374                 Intent logcatIntent = new Intent(this, LogcatActivity.class);
2375                 startActivity(logcatIntent);
2376                 break;
2377
2378             case R.id.guide:
2379                 // Launch `GuideActivity`.
2380                 Intent guideIntent = new Intent(this, GuideActivity.class);
2381                 startActivity(guideIntent);
2382                 break;
2383
2384             case R.id.about:
2385                 // Launch `AboutActivity`.
2386                 Intent aboutIntent = new Intent(this, AboutActivity.class);
2387                 startActivity(aboutIntent);
2388                 break;
2389         }
2390
2391         // Get a handle for the drawer layout.
2392         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2393
2394         // Close the navigation drawer.
2395         drawerLayout.closeDrawer(GravityCompat.START);
2396         return true;
2397     }
2398
2399     @Override
2400     public void onPostCreate(Bundle savedInstanceState) {
2401         // Run the default commands.
2402         super.onPostCreate(savedInstanceState);
2403
2404         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
2405         actionBarDrawerToggle.syncState();
2406     }
2407
2408     @Override
2409     public void onConfigurationChanged(Configuration newConfig) {
2410         // Run the default commands.
2411         super.onConfigurationChanged(newConfig);
2412
2413         // Get the status bar pixel size.
2414         int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
2415         int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
2416
2417         // Get the resource density.
2418         float screenDensity = getResources().getDisplayMetrics().density;
2419
2420         // Recalculate the drawer header padding.
2421         drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
2422         drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
2423         drawerHeaderPaddingBottom = (int) (8 * screenDensity);
2424
2425         // Reload the ad for the free flavor if not in full screen mode.
2426         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2427             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2428             AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
2429         }
2430
2431         // `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:
2432         // https://code.google.com/p/android/issues/detail?id=20493#c8
2433         // ActivityCompat.invalidateOptionsMenu(this);
2434     }
2435
2436     @Override
2437     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2438         // Store the hit test result.
2439         final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2440
2441         // Create the URL strings.
2442         final String imageUrl;
2443         final String linkUrl;
2444
2445         // Get handles for the the clipboard and fragment managers.
2446         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2447         FragmentManager fragmentManager = getSupportFragmentManager();
2448
2449         // Remove the lint errors below that the clipboard manager might be null.
2450         assert clipboardManager != null;
2451
2452         // Process the link according to the type.
2453         switch (hitTestResult.getType()) {
2454             // `SRC_ANCHOR_TYPE` is a link.
2455             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2456                 // Get the target URL.
2457                 linkUrl = hitTestResult.getExtra();
2458
2459                 // Set the target URL as the title of the `ContextMenu`.
2460                 menu.setHeaderTitle(linkUrl);
2461
2462                 // Add a Load URL entry.
2463                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2464                     // Add a new tab.
2465                     addTab(null);
2466
2467                     // Load the URL.
2468                     loadUrl(linkUrl);
2469                     return false;
2470                 });
2471
2472                 // Add an Open with App entry.
2473                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2474                     openWithApp(linkUrl);
2475                     return false;
2476                 });
2477
2478                 // Add an Open with Browser entry.
2479                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2480                     openWithBrowser(linkUrl);
2481                     return false;
2482                 });
2483
2484                 // Add a Copy URL entry.
2485                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2486                     // Save the link URL in a `ClipData`.
2487                     ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2488
2489                     // Set the `ClipData` as the clipboard's primary clip.
2490                     clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2491                     return false;
2492                 });
2493
2494                 // Add a Download URL entry.
2495                 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
2496                     // Check if the download should be processed by an external app.
2497                     if (downloadWithExternalApp) {  // Download with an external app.
2498                         openUrlWithExternalApp(linkUrl);
2499                     } else {  // Download with Android's download manager.
2500                         // Check to see if the storage permission has already been granted.
2501                         if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
2502                             // Store the variables for future use by `onRequestPermissionsResult()`.
2503                             downloadUrl = linkUrl;
2504                             downloadContentDisposition = "none";
2505                             downloadContentLength = -1;
2506
2507                             // Show a dialog if the user has previously denied the permission.
2508                             if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
2509                                 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
2510                                 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
2511
2512                                 // Show the download location permission alert dialog.  The permission will be requested when the the dialog is closed.
2513                                 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
2514                             } else {  // Show the permission request directly.
2515                                 // Request the permission.  The download dialog will be launched by `onRequestPermissionResult()`.
2516                                 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
2517                             }
2518                         } else {  // The storage permission has already been granted.
2519                             // Get a handle for the download file alert dialog.
2520                             DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
2521
2522                             // Show the download file alert dialog.
2523                             downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
2524                         }
2525                     }
2526                     return false;
2527                 });
2528
2529                 // Add a Cancel entry, which by default closes the context menu.
2530                 menu.add(R.string.cancel);
2531                 break;
2532
2533             case WebView.HitTestResult.EMAIL_TYPE:
2534                 // Get the target URL.
2535                 linkUrl = hitTestResult.getExtra();
2536
2537                 // Set the target URL as the title of the `ContextMenu`.
2538                 menu.setHeaderTitle(linkUrl);
2539
2540                 // Add a Write Email entry.
2541                 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2542                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2543                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2544
2545                     // Parse the url and set it as the data for the `Intent`.
2546                     emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2547
2548                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2549                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2550
2551                     // Make it so.
2552                     startActivity(emailIntent);
2553                     return false;
2554                 });
2555
2556                 // Add a Copy Email Address entry.
2557                 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2558                     // Save the email address in a `ClipData`.
2559                     ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2560
2561                     // Set the `ClipData` as the clipboard's primary clip.
2562                     clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2563                     return false;
2564                 });
2565
2566                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
2567                 menu.add(R.string.cancel);
2568                 break;
2569
2570             // `IMAGE_TYPE` is an image.
2571             case WebView.HitTestResult.IMAGE_TYPE:
2572                 // Get the image URL.
2573                 imageUrl = hitTestResult.getExtra();
2574
2575                 // Set the image URL as the title of the `ContextMenu`.
2576                 menu.setHeaderTitle(imageUrl);
2577
2578                 // Add a View Image entry.
2579                 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2580                     loadUrl(imageUrl);
2581                     return false;
2582                 });
2583
2584                 // Add a Download Image entry.
2585                 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
2586                     // Check if the download should be processed by an external app.
2587                     if (downloadWithExternalApp) {  // Download with an external app.
2588                         openUrlWithExternalApp(imageUrl);
2589                     } else {  // Download with Android's download manager.
2590                         // Check to see if the storage permission has already been granted.
2591                         if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {  // The storage permission needs to be requested.
2592                             // Store the image URL for use by `onRequestPermissionResult()`.
2593                             downloadImageUrl = imageUrl;
2594
2595                             // Show a dialog if the user has previously denied the permission.
2596                             if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {  // Show a dialog explaining the request first.
2597                                 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
2598                                 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
2599
2600                                 // Show the download location permission alert dialog.  The permission will be requested when the dialog is closed.
2601                                 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
2602                             } else {  // Show the permission request directly.
2603                                 // Request the permission.  The download dialog will be launched by `onRequestPermissionResult().
2604                                 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
2605                             }
2606                         } else {  // The storage permission has already been granted.
2607                             // Get a handle for the download image alert dialog.
2608                             DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
2609
2610                             // Show the download image alert dialog.
2611                             downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
2612                         }
2613                     }
2614                     return false;
2615                 });
2616
2617                 // Add a Copy URL entry.
2618                 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
2619                     // Save the image URL in a `ClipData`.
2620                     ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);