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