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