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