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