b2d9afa3fa48e3b801c6b293b912aa2d637910f6
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
1 /*
2  * Copyright © 2015-2020 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.Dialog;
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.database.Cursor;
41 import android.graphics.Bitmap;
42 import android.graphics.BitmapFactory;
43 import android.graphics.Typeface;
44 import android.graphics.drawable.BitmapDrawable;
45 import android.graphics.drawable.Drawable;
46 import android.net.Uri;
47 import android.net.http.SslCertificate;
48 import android.net.http.SslError;
49 import android.os.AsyncTask;
50 import android.os.Build;
51 import android.os.Bundle;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.preference.PreferenceManager;
55 import android.print.PrintDocumentAdapter;
56 import android.print.PrintManager;
57 import android.text.Editable;
58 import android.text.Spanned;
59 import android.text.TextWatcher;
60 import android.text.style.ForegroundColorSpan;
61 import android.util.Patterns;
62 import android.util.TypedValue;
63 import android.view.ContextMenu;
64 import android.view.GestureDetector;
65 import android.view.KeyEvent;
66 import android.view.Menu;
67 import android.view.MenuItem;
68 import android.view.MotionEvent;
69 import android.view.View;
70 import android.view.ViewGroup;
71 import android.view.WindowManager;
72 import android.view.inputmethod.InputMethodManager;
73 import android.webkit.CookieManager;
74 import android.webkit.HttpAuthHandler;
75 import android.webkit.SslErrorHandler;
76 import android.webkit.ValueCallback;
77 import android.webkit.WebBackForwardList;
78 import android.webkit.WebChromeClient;
79 import android.webkit.WebResourceResponse;
80 import android.webkit.WebSettings;
81 import android.webkit.WebStorage;
82 import android.webkit.WebView;
83 import android.webkit.WebViewClient;
84 import android.webkit.WebViewDatabase;
85 import android.widget.ArrayAdapter;
86 import android.widget.CursorAdapter;
87 import android.widget.EditText;
88 import android.widget.FrameLayout;
89 import android.widget.ImageView;
90 import android.widget.LinearLayout;
91 import android.widget.ListView;
92 import android.widget.ProgressBar;
93 import android.widget.RadioButton;
94 import android.widget.RelativeLayout;
95 import android.widget.TextView;
96
97 import androidx.annotation.NonNull;
98 import androidx.appcompat.app.ActionBar;
99 import androidx.appcompat.app.ActionBarDrawerToggle;
100 import androidx.appcompat.app.AppCompatActivity;
101 import androidx.appcompat.app.AppCompatDelegate;
102 import androidx.appcompat.widget.Toolbar;
103 import androidx.coordinatorlayout.widget.CoordinatorLayout;
104 import androidx.core.app.ActivityCompat;
105 import androidx.core.content.ContextCompat;
106 import androidx.core.content.res.ResourcesCompat;
107 import androidx.core.view.GravityCompat;
108 import androidx.drawerlayout.widget.DrawerLayout;
109 import androidx.fragment.app.DialogFragment;
110 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
111 import androidx.viewpager.widget.ViewPager;
112 import androidx.webkit.WebSettingsCompat;
113 import androidx.webkit.WebViewFeature;
114
115 import com.google.android.material.appbar.AppBarLayout;
116 import com.google.android.material.floatingactionbutton.FloatingActionButton;
117 import com.google.android.material.navigation.NavigationView;
118 import com.google.android.material.snackbar.Snackbar;
119 import com.google.android.material.tabs.TabLayout;
120
121 import com.stoutner.privacybrowser.BuildConfig;
122 import com.stoutner.privacybrowser.R;
123 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
124 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
125 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
126 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
127 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
128 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
129 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
130 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
131 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
132 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
133 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
134 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
135 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
136 import com.stoutner.privacybrowser.dialogs.OpenDialog;
137 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
138 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
139 import com.stoutner.privacybrowser.dialogs.SaveDialog;
140 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
141 import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
142 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
143 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
144 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
145 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
146 import com.stoutner.privacybrowser.helpers.AdHelper;
147 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
148 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
149 import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
150 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
151 import com.stoutner.privacybrowser.helpers.FileNameHelper;
152 import com.stoutner.privacybrowser.helpers.ProxyHelper;
153 import com.stoutner.privacybrowser.views.NestedScrollWebView;
154
155 import java.io.ByteArrayInputStream;
156 import java.io.ByteArrayOutputStream;
157 import java.io.File;
158 import java.io.IOException;
159 import java.io.UnsupportedEncodingException;
160 import java.net.MalformedURLException;
161 import java.net.URL;
162 import java.net.URLDecoder;
163 import java.net.URLEncoder;
164 import java.text.NumberFormat;
165 import java.util.ArrayList;
166 import java.util.Date;
167 import java.util.HashMap;
168 import java.util.HashSet;
169 import java.util.List;
170 import java.util.Map;
171 import java.util.Objects;
172 import java.util.Set;
173 import java.util.concurrent.ExecutorService;
174 import java.util.concurrent.Executors;
175
176 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
177         EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
178         PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
179         UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
180
181     // The executor service handles background tasks.  It is accessed from `ViewSourceActivity`.
182     public static ExecutorService executorService = Executors.newFixedThreadPool(4);
183
184     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
185     public static String orbotStatus = "unknown";
186
187     // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`.  It is also used in `onCreate()`, `onResume()`, and `addTab()`.
188     public static WebViewPagerAdapter webViewPagerAdapter;
189
190     // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`.  They are used in `onRestart()`.
191     public static boolean loadUrlOnRestart;
192     public static String urlToLoadOnRestart;
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     // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
198     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
199     public static String currentBookmarksFolder;
200
201     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
202     public final static int UNRECOGNIZED_USER_AGENT = -1;
203     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
204     public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
205     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
206     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
207     public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
208
209     // Start activity for result request codes.  The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
210     public final static int BROWSE_OPEN_REQUEST_CODE = 0;
211     public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1;
212     private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2;
213
214     // The proxy mode is public static so it can be accessed from `ProxyHelper()`.
215     // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
216     // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
217     public static String proxyMode = ProxyHelper.NONE;
218
219
220     // The permission result request codes are used in `onCreateContextMenu()`, `onRequestPermissionResult()`, `onSaveWebpage()`, `onCloseStoragePermissionDialog()`, and `initializeWebView()`.
221     private final int PERMISSION_OPEN_REQUEST_CODE = 0;
222     private final int PERMISSION_SAVE_URL_REQUEST_CODE = 1;
223     private final int PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE = 2;
224     private final int PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE = 3;
225
226     // Define the saved instance state constants.
227     private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
228     private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
229     private final String SAVED_TAB_POSITION = "saved_tab_position";
230     private final String PROXY_MODE = "proxy_mode";
231
232     // Define the saved instance state variables.
233     private ArrayList<Bundle> savedStateArrayList;
234     private ArrayList<Bundle> savedNestedScrollWebViewStateArrayList;
235     private int savedTabPosition;
236     private String savedProxyMode;
237
238     // Define the class views.
239     private AppBarLayout appBarLayout;
240     private TabLayout tabLayout;
241     private ViewPager webViewPager;
242
243     // Define the class variables.
244     @SuppressWarnings("rawtypes")
245     AsyncTask populateBlocklists;
246
247     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
248     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
249     private NestedScrollWebView currentWebView;
250
251     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
252     private final Map<String, String> customHeaders = new HashMap<>();
253
254     // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
255     private String searchURL;
256
257     // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
258     private Menu optionsMenu;
259
260     // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
261     private ArrayList<List<String[]>> easyList;
262     private ArrayList<List<String[]>> easyPrivacy;
263     private ArrayList<List<String[]>> fanboysAnnoyanceList;
264     private ArrayList<List<String[]>> fanboysSocialList;
265     private ArrayList<List<String[]>> ultraList;
266     private ArrayList<List<String[]>> ultraPrivacy;
267
268     // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
269     private String webViewDefaultUserAgent;
270
271     // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
272     private boolean incognitoModeEnabled;
273
274     // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
275     private boolean fullScreenBrowsingModeEnabled;
276
277     // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
278     private boolean inFullScreenBrowsingMode;
279
280     // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`.
281     private boolean hideAppBar;
282     private boolean scrollAppBar;
283
284     // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
285     private boolean loadingNewIntent;
286
287     // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
288     private boolean reapplyDomainSettingsOnRestart;
289
290     // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
291     private boolean reapplyAppSettingsOnRestart;
292
293     // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
294     private boolean displayingFullScreenVideo;
295
296     // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
297     private BroadcastReceiver orbotStatusBroadcastReceiver;
298
299     // The waiting for proxy boolean is used in `onResume()`, `initializeApp()` and `applyProxy()`.
300     private boolean waitingForProxy = false;
301
302     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
303     private ActionBarDrawerToggle actionBarDrawerToggle;
304
305     // The color spans are used in `onCreate()` and `highlightUrlText()`.
306     private ForegroundColorSpan redColorSpan;
307     private ForegroundColorSpan initialGrayColorSpan;
308     private ForegroundColorSpan finalGrayColorSpan;
309
310     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
311     // and `loadBookmarksFolder()`.
312     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
313
314     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
315     private Cursor bookmarksCursor;
316
317     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
318     private CursorAdapter bookmarksCursorAdapter;
319
320     // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
321     private String oldFolderNameString;
322
323     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
324     private ValueCallback<Uri[]> fileChooserCallback;
325
326     // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
327     private int appBarHeight;
328     private int defaultProgressViewStartOffset;
329     private int defaultProgressViewEndOffset;
330
331     // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
332     private boolean sanitizeGoogleAnalytics;
333     private boolean sanitizeFacebookClickIds;
334     private boolean sanitizeTwitterAmpRedirects;
335
336     // The file path strings are used in `onSaveWebpage()` and `onRequestPermissionResult()`
337     private String openFilePath;
338     private String saveWebpageUrl;
339     private String saveWebpageFilePath;
340
341     @Override
342     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
343     @SuppressLint("ClickableViewAccessibility")
344     protected void onCreate(Bundle savedInstanceState) {
345         // Run the default commands.
346         super.onCreate(savedInstanceState);
347
348         // Check to see if the activity has been restarted.
349         if (savedInstanceState != null) {
350             // Store the saved instance state variables.
351             savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
352             savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
353             savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
354             savedProxyMode = savedInstanceState.getString(PROXY_MODE);
355         }
356
357         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
358         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
359
360         // Get a handle for the shared preferences.
361         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
362
363         // Get the screenshot preference.
364         String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
365         boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
366
367         // Get the theme entry values string array.
368         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
369
370         // Set the app theme according to the preference.  A switch statement cannot be used because the theme entry values string array is not a compile time constant.
371         if (appTheme.equals(appThemeEntryValuesStringArray[1])) {  // The light theme is selected.
372             // Apply the light theme.
373             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
374         } else if (appTheme.equals(appThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
375             // Apply the dark theme.
376             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
377         } else {  // The system default theme is selected.
378             if (Build.VERSION.SDK_INT >= 28) {  // The system default theme is supported.
379                 // Follow the system default theme.
380                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
381             } else {  // The system default theme is not supported.
382                 // Follow the battery saver mode.
383                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
384             }
385         }
386
387         // Disable screenshots if not allowed.
388         if (!allowScreenshots) {
389             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
390         }
391
392         // Enable the drawing of the entire webpage.  This makes it possible to save a website image.  This must be done before anything else happens with the WebView.
393         if (Build.VERSION.SDK_INT >= 21) {
394             WebView.enableSlowWholeDocumentDraw();
395         }
396
397         // Set the theme.
398         setTheme(R.style.PrivacyBrowser);
399
400         // Set the content view.
401         setContentView(R.layout.main_framelayout);
402
403         // Get handles for the views.
404         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
405         appBarLayout = findViewById(R.id.appbar_layout);
406         Toolbar toolbar = findViewById(R.id.toolbar);
407         tabLayout = findViewById(R.id.tablayout);
408         webViewPager = findViewById(R.id.webviewpager);
409
410         // Get a handle for the app compat delegate.
411         AppCompatDelegate appCompatDelegate = getDelegate();
412
413         // Set the support action bar.
414         appCompatDelegate.setSupportActionBar(toolbar);
415
416         // Get a handle for the action bar.
417         ActionBar actionBar = appCompatDelegate.getSupportActionBar();
418
419         // This is needed to get rid of the Android Studio warning that the action bar might be null.
420         assert actionBar != null;
421
422         // Add the custom layout, which shows the URL text bar.
423         actionBar.setCustomView(R.layout.url_app_bar);
424         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
425
426         // Create the hamburger icon at the start of the AppBar.
427         actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
428
429         // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
430         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
431
432         // Initialize the web view pager adapter.
433         webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
434
435         // Set the pager adapter on the web view pager.
436         webViewPager.setAdapter(webViewPagerAdapter);
437
438         // Store up to 100 tabs in memory.
439         webViewPager.setOffscreenPageLimit(100);
440
441         // Initialize the app.
442         initializeApp();
443
444         // Apply the app settings from the shared preferences.
445         applyAppSettings();
446
447         // Populate the blocklists.
448         populateBlocklists = new PopulateBlocklists(this, this).execute();
449     }
450
451     @Override
452     protected void onNewIntent(Intent intent) {
453         // Run the default commands.
454         super.onNewIntent(intent);
455
456         // Replace the intent that started the app with this one.
457         setIntent(intent);
458
459         // Check to see if the app is being restarted.
460         if (savedStateArrayList == null || savedStateArrayList.size() == 0) {  // The activity is running for the first time.
461             // Get the information from the intent.
462             String intentAction = intent.getAction();
463             Uri intentUriData = intent.getData();
464
465             // Determine if this is a web search.
466             boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
467
468             // Only process the URI if it contains data or it is a web search.  If the user pressed the desktop icon after the app was already running the URI will be null.
469             if (intentUriData != null || isWebSearch) {
470                 // Get the shared preferences.
471                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
472
473                 // Create a URL string.
474                 String url;
475
476                 // If the intent action is a web search, perform the search.
477                 if (isWebSearch) {  // The intent is a web search.
478                     // Create an encoded URL string.
479                     String encodedUrlString;
480
481                     // Sanitize the search input and convert it to a search.
482                     try {
483                         encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
484                     } catch (UnsupportedEncodingException exception) {
485                         encodedUrlString = "";
486                     }
487
488                     // Add the base search URL.
489                     url = searchURL + encodedUrlString;
490                 } else {  // The intent should contain a URL.
491                     // Set the intent data as the URL.
492                     url = intentUriData.toString();
493                 }
494
495                 // Add a new tab if specified in the preferences.
496                 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
497                     // Set the loading new intent flag.
498                     loadingNewIntent = true;
499
500                     // Add a new tab.
501                     addNewTab(url, true);
502                 } else {  // Load the URL in the current tab.
503                     // Make it so.
504                     loadUrl(currentWebView, url);
505                 }
506
507                 // Get a handle for the drawer layout.
508                 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
509
510                 // Close the navigation drawer if it is open.
511                 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
512                     drawerLayout.closeDrawer(GravityCompat.START);
513                 }
514
515                 // Close the bookmarks drawer if it is open.
516                 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
517                     drawerLayout.closeDrawer(GravityCompat.END);
518                 }
519             }
520         }
521     }
522
523     @Override
524     public void onRestart() {
525         // Run the default commands.
526         super.onRestart();
527
528         // Apply the app settings if returning from the Settings activity.
529         if (reapplyAppSettingsOnRestart) {
530             // Reset the reapply app settings on restart tracker.
531             reapplyAppSettingsOnRestart = false;
532
533             // Apply the app settings.
534             applyAppSettings();
535         }
536
537         // Apply the domain settings if returning from the settings or domains activity.
538         if (reapplyDomainSettingsOnRestart) {
539             // Reset the reapply domain settings on restart tracker.
540             reapplyDomainSettingsOnRestart = false;
541
542             // Reapply the domain settings for each tab.
543             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
544                 // Get the WebView tab fragment.
545                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
546
547                 // Get the fragment view.
548                 View fragmentView = webViewTabFragment.getView();
549
550                 // Only reload the WebViews if they exist.
551                 if (fragmentView != null) {
552                     // Get the nested scroll WebView from the tab fragment.
553                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
554
555                     // Reset the current domain name so the domain settings will be reapplied.
556                     nestedScrollWebView.resetCurrentDomainName();
557
558                     // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
559                     if (nestedScrollWebView.getUrl() != null) {
560                         applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
561                     }
562                 }
563             }
564         }
565
566         // Load the URL on restart (used when loading a bookmark).
567         if (loadUrlOnRestart) {
568             // Load the specified URL.
569             loadUrl(currentWebView, urlToLoadOnRestart);
570
571             // Reset the load on restart tracker.
572             loadUrlOnRestart = false;
573         }
574
575         // Update the bookmarks drawer if returning from the Bookmarks activity.
576         if (restartFromBookmarksActivity) {
577             // Get a handle for the drawer layout.
578             DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
579
580             // Close the bookmarks drawer.
581             drawerLayout.closeDrawer(GravityCompat.END);
582
583             // Reload the bookmarks drawer.
584             loadBookmarksFolder();
585
586             // Reset `restartFromBookmarksActivity`.
587             restartFromBookmarksActivity = false;
588         }
589
590         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
591         updatePrivacyIcons(true);
592     }
593
594     // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
595     @Override
596     public void onResume() {
597         // Run the default commands.
598         super.onResume();
599
600         // Resume any WebViews.
601         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
602             // Get the WebView tab fragment.
603             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
604
605             // Get the fragment view.
606             View fragmentView = webViewTabFragment.getView();
607
608             // Only resume the WebViews if they exist (they won't when the app is first created).
609             if (fragmentView != null) {
610                 // Get the nested scroll WebView from the tab fragment.
611                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
612
613                 // Resume the nested scroll WebView JavaScript timers.
614                 nestedScrollWebView.resumeTimers();
615
616                 // Resume the nested scroll WebView.
617                 nestedScrollWebView.onResume();
618             }
619         }
620
621         // Reapply the proxy settings if the system is using a proxy.  This redisplays the appropriate alert dialog.
622         if (!proxyMode.equals(ProxyHelper.NONE)) {
623             applyProxy(false);
624         }
625
626         // Reapply any system UI flags and the ad in the free flavor.
627         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {  // The system is displaying a website or a video in full screen mode.
628             // Get a handle for the root frame layouts.
629             FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
630
631             /* Hide the system bars.
632              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
633              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
634              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
635              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
636              */
637             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
638                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
639         } else if (BuildConfig.FLAVOR.contentEquals("free")) {  // The system in not in full screen mode.
640             // Resume the ad.
641             AdHelper.resumeAd(findViewById(R.id.adview));
642         }
643     }
644
645     @Override
646     public void onPause() {
647         // Run the default commands.
648         super.onPause();
649
650         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
651             // Get the WebView tab fragment.
652             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
653
654             // Get the fragment view.
655             View fragmentView = webViewTabFragment.getView();
656
657             // Only pause the WebViews if they exist (they won't when the app is first created).
658             if (fragmentView != null) {
659                 // Get the nested scroll WebView from the tab fragment.
660                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
661
662                 // Pause the nested scroll WebView.
663                 nestedScrollWebView.onPause();
664
665                 // Pause the nested scroll WebView JavaScript timers.
666                 nestedScrollWebView.pauseTimers();
667             }
668         }
669
670         // Pause the ad or it will continue to consume resources in the background on the free flavor.
671         if (BuildConfig.FLAVOR.contentEquals("free")) {
672             // Pause the ad.
673             AdHelper.pauseAd(findViewById(R.id.adview));
674         }
675     }
676
677     @Override
678     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
679         // Run the default commands.
680         super.onSaveInstanceState(savedInstanceState);
681
682         // Create the saved state array lists.
683         ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
684         ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
685
686         // Get the URLs from each tab.
687         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
688             // Get the WebView tab fragment.
689             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
690
691             // Get the fragment view.
692             View fragmentView = webViewTabFragment.getView();
693
694             if (fragmentView != null) {
695                 // Get the nested scroll WebView from the tab fragment.
696                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
697
698                 // Create saved state bundle.
699                 Bundle savedStateBundle = new Bundle();
700
701                 // Get the current states.
702                 nestedScrollWebView.saveState(savedStateBundle);
703                 Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
704
705                 // Store the saved states in the array lists.
706                 savedStateArrayList.add(savedStateBundle);
707                 savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
708             }
709         }
710
711         // Get the current tab position.
712         int currentTabPosition = tabLayout.getSelectedTabPosition();
713
714         // Store the saved states in the bundle.
715         savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
716         savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
717         savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
718         savedInstanceState.putString(PROXY_MODE, proxyMode);
719     }
720
721     @Override
722     public void onDestroy() {
723         // Unregister the orbot status broadcast receiver if it exists.
724         if (orbotStatusBroadcastReceiver != null) {
725             this.unregisterReceiver(orbotStatusBroadcastReceiver);
726         }
727
728         // Close the bookmarks cursor if it exists.
729         if (bookmarksCursor != null) {
730             bookmarksCursor.close();
731         }
732
733         // Close the bookmarks database if it exists.
734         if (bookmarksDatabaseHelper != null) {
735             bookmarksDatabaseHelper.close();
736         }
737
738         // Stop populating the blocklists if the AsyncTask is running in the background.
739         if (populateBlocklists != null) {
740             populateBlocklists.cancel(true);
741         }
742
743         // Run the default commands.
744         super.onDestroy();
745     }
746
747     @Override
748     public boolean onCreateOptionsMenu(Menu menu) {
749         // Inflate the menu.  This adds items to the action bar if it is present.
750         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
751
752         // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
753         optionsMenu = menu;
754
755         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
756         updatePrivacyIcons(false);
757
758         // Get handles for the menu items.
759         MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
760         MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
761         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
762         MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
763         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
764         MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
765         MenuItem darkWebViewMenuItem = menu.findItem(R.id.dark_webview);
766         MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
767
768         // Only display third-party cookies if API >= 21
769         toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
770
771         // Only display the form data menu items if the API < 26.
772         toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
773         clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
774
775         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
776         clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
777
778         // Only display the dark WebView menu item if API >= 21.
779         darkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
780
781         // Only show Ad Consent if this is the free flavor.
782         adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
783
784         // Get the shared preferences.
785         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
786
787         // Get the dark theme and app bar preferences..
788         boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
789
790         // 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.
791         if (displayAdditionalAppBarIcons) {
792             toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
793             toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
794             refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
795         } else { //Do not display the additional icons.
796             toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
797             toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
798             refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
799         }
800
801         // Replace Refresh with Stop if a URL is already loading.
802         if (currentWebView != null && currentWebView.getProgress() != 100) {
803             // Set the title.
804             refreshMenuItem.setTitle(R.string.stop);
805
806             // Set the icon if it is displayed in the app bar.
807             if (displayAdditionalAppBarIcons) {
808                 // Get the current theme status.
809                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
810
811                 // Set the icon according to the current theme status.
812                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
813                     refreshMenuItem.setIcon(R.drawable.close_day);
814                 } else {
815                     refreshMenuItem.setIcon(R.drawable.close_night);
816                 }
817             }
818         }
819
820         // Done.
821         return true;
822     }
823
824     @Override
825     public boolean onPrepareOptionsMenu(Menu menu) {
826         // Get handles for the menu items.
827         MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
828         MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
829         MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
830         MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
831         MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);  // Form data can be removed once the minimum API >= 26.
832         MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
833         MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
834         MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
835         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
836         MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
837         MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
838         MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
839         MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
840         MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
841         MenuItem ultraListMenuItem = menu.findItem(R.id.ultralist);
842         MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
843         MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
844         MenuItem proxyMenuItem = menu.findItem(R.id.proxy);
845         MenuItem userAgentMenuItem = menu.findItem(R.id.user_agent);
846         MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
847         MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
848         MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
849         MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
850         MenuItem darkWebViewMenuItem = menu.findItem(R.id.dark_webview);
851
852         // Get a handle for the cookie manager.
853         CookieManager cookieManager = CookieManager.getInstance();
854
855         // Initialize the current user agent string and the font size.
856         String currentUserAgent = getString(R.string.user_agent_privacy_browser);
857         int fontSize = 100;
858
859         // Set items that require the current web view to be populated.  It will be null when the program is first opened, as `onPrepareOptionsMenu()` is called before the first WebView is initialized.
860         if (currentWebView != null) {
861             // Set the add or edit domain text.
862             if (currentWebView.getDomainSettingsApplied()) {
863                 addOrEditDomain.setTitle(R.string.edit_domain_settings);
864             } else {
865                 addOrEditDomain.setTitle(R.string.add_domain_settings);
866             }
867
868             // Get the current user agent from the WebView.
869             currentUserAgent = currentWebView.getSettings().getUserAgentString();
870
871             // Get the current font size from the
872             fontSize = currentWebView.getSettings().getTextZoom();
873
874             // Set the status of the menu item checkboxes.
875             domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
876             saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
877             easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
878             easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
879             fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
880             fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
881             ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
882             ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
883             blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
884             swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
885             wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
886             displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
887
888             // Initialize the display names for the blocklists with the number of blocked requests.
889             blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
890             easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
891             easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
892             fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
893             fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
894             ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
895             ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
896             blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
897
898             // Only modify third-party cookies if the API >= 21.
899             if (Build.VERSION.SDK_INT >= 21) {
900                 // Set the status of the third-party cookies checkbox.
901                 thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
902
903                 // Enable third-party cookies if first-party cookies are enabled.
904                 thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
905             }
906
907             // Enable DOM Storage if JavaScript is enabled.
908             domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
909
910             // Set the checkbox status for dark WebView if the WebView supports it.
911             if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
912                 darkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
913             }
914         }
915
916         // Set the checked status of the first party cookies menu item.
917         firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
918
919         // Enable Clear Cookies if there are any.
920         clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
921
922         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
923         String privateDataDirectoryString = getApplicationInfo().dataDir;
924
925         // Get a count of the number of files in the Local Storage directory.
926         File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
927         int localStorageDirectoryNumberOfFiles = 0;
928         if (localStorageDirectory.exists()) {
929             // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
930             localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
931         }
932
933         // Get a count of the number of files in the IndexedDB directory.
934         File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
935         int indexedDBDirectoryNumberOfFiles = 0;
936         if (indexedDBDirectory.exists()) {
937             // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
938             indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
939         }
940
941         // Enable Clear DOM Storage if there is any.
942         clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
943
944         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
945         if (Build.VERSION.SDK_INT < 26) {
946             // Get the WebView database.
947             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
948
949             // Enable the clear form data menu item if there is anything to clear.
950             clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
951         }
952
953         // Enable Clear Data if any of the submenu items are enabled.
954         clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
955
956         // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
957         fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
958
959         // Set the proxy title and check the applied proxy.
960         switch (proxyMode) {
961             case ProxyHelper.NONE:
962                 // Set the proxy title.
963                 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
964
965                 // Check the proxy None radio button.
966                 menu.findItem(R.id.proxy_none).setChecked(true);
967                 break;
968
969             case ProxyHelper.TOR:
970                 // Set the proxy title.
971                 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
972
973                 // Check the proxy Tor radio button.
974                 menu.findItem(R.id.proxy_tor).setChecked(true);
975                 break;
976
977             case ProxyHelper.I2P:
978                 // Set the proxy title.
979                 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
980
981                 // Check the proxy I2P radio button.
982                 menu.findItem(R.id.proxy_i2p).setChecked(true);
983                 break;
984
985             case ProxyHelper.CUSTOM:
986                 // Set the proxy title.
987                 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
988
989                 // Check the proxy Custom radio button.
990                 menu.findItem(R.id.proxy_custom).setChecked(true);
991                 break;
992         }
993
994         // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
995         if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
996             // Update the user agent menu item title.
997             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
998
999             // Select the Privacy Browser radio box.
1000             menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
1001         } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
1002             // Update the user agent menu item title.
1003             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
1004
1005             // Select the WebView Default radio box.
1006             menu.findItem(R.id.user_agent_webview_default).setChecked(true);
1007         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
1008             // Update the user agent menu item title.
1009             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
1010
1011             // Select the Firefox on Android radio box.
1012             menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
1013         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
1014             // Update the user agent menu item title.
1015             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
1016
1017             // Select the Chrome on Android radio box.
1018             menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
1019         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
1020             // Update the user agent menu item title.
1021             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
1022
1023             // Select the Safari on iOS radio box.
1024             menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
1025         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
1026             // Update the user agent menu item title.
1027             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
1028
1029             // Select the Firefox on Linux radio box.
1030             menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
1031         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
1032             // Update the user agent menu item title.
1033             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
1034
1035             // Select the Chromium on Linux radio box.
1036             menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
1037         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
1038             // Update the user agent menu item title.
1039             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
1040
1041             // Select the Firefox on Windows radio box.
1042             menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
1043         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
1044             // Update the user agent menu item title.
1045             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
1046
1047             // Select the Chrome on Windows radio box.
1048             menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
1049         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
1050             // Update the user agent menu item title.
1051             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
1052
1053             // Select the Edge on Windows radio box.
1054             menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
1055         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
1056             // Update the user agent menu item title.
1057             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
1058
1059             // Select the Internet on Windows radio box.
1060             menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
1061         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
1062             // Update the user agent menu item title.
1063             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
1064
1065             // Select the Safari on macOS radio box.
1066             menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
1067         } else {  // Custom user agent.
1068             // Update the user agent menu item title.
1069             userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
1070
1071             // Select the Custom radio box.
1072             menu.findItem(R.id.user_agent_custom).setChecked(true);
1073         }
1074
1075         // Set the font size title.
1076         fontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
1077
1078         // Run all the other default commands.
1079         super.onPrepareOptionsMenu(menu);
1080
1081         // Display the menu.
1082         return true;
1083     }
1084
1085     @Override
1086     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
1087     @SuppressLint("SetJavaScriptEnabled")
1088     public boolean onOptionsItemSelected(MenuItem menuItem) {
1089         // Get the selected menu item ID.
1090         int menuItemId = menuItem.getItemId();
1091
1092         // Get a handle for the shared preferences.
1093         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1094
1095         // Get a handle for the cookie manager.
1096         CookieManager cookieManager = CookieManager.getInstance();
1097
1098         // Run the commands that correlate to the selected menu item.
1099         switch (menuItemId) {
1100             case R.id.toggle_javascript:
1101                 // Toggle the JavaScript status.
1102                 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1103
1104                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1105                 updatePrivacyIcons(true);
1106
1107                 // Display a `Snackbar`.
1108                 if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
1109                     Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1110                 } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
1111                     Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1112                 } else {  // Privacy mode.
1113                     Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1114                 }
1115
1116                 // Reload the current WebView.
1117                 currentWebView.reload();
1118
1119                 // Consume the event.
1120                 return true;
1121
1122             case R.id.refresh:
1123                 if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
1124                     // Reload the current WebView.
1125                     currentWebView.reload();
1126                 } else {  // The stop button was pushed.
1127                     // Stop the loading of the WebView.
1128                     currentWebView.stopLoading();
1129                 }
1130
1131                 // Consume the event.
1132                 return true;
1133
1134             case R.id.bookmarks:
1135                 // Get a handle for the drawer layout.
1136                 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1137
1138                 // Open the bookmarks drawer.
1139                 drawerLayout.openDrawer(GravityCompat.END);
1140
1141                 // Consume the event.
1142                 return true;
1143
1144             case R.id.toggle_first_party_cookies:
1145                 // Switch the first-party cookie status.
1146                 cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1147
1148                 // Store the first-party cookie status.
1149                 currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
1150
1151                 // Update the menu checkbox.
1152                 menuItem.setChecked(cookieManager.acceptCookie());
1153
1154                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1155                 updatePrivacyIcons(true);
1156
1157                 // Display a snackbar.
1158                 if (cookieManager.acceptCookie()) {  // First-party cookies are enabled.
1159                     Snackbar.make(webViewPager, R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1160                 } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
1161                     Snackbar.make(webViewPager, R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1162                 } else {  // Privacy mode.
1163                     Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1164                 }
1165
1166                 // Reload the current WebView.
1167                 currentWebView.reload();
1168
1169                 // Consume the event.
1170                 return true;
1171
1172             case R.id.toggle_third_party_cookies:
1173                 if (Build.VERSION.SDK_INT >= 21) {
1174                     // Switch the status of thirdPartyCookiesEnabled.
1175                     cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
1176
1177                     // Update the menu checkbox.
1178                     menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1179
1180                     // Display a snackbar.
1181                     if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
1182                         Snackbar.make(webViewPager, R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1183                     } else {
1184                         Snackbar.make(webViewPager, R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1185                     }
1186
1187                     // Reload the current WebView.
1188                     currentWebView.reload();
1189                 } // Else do nothing because SDK < 21.
1190
1191                 // Consume the event.
1192                 return true;
1193
1194             case R.id.toggle_dom_storage:
1195                 // Toggle the status of domStorageEnabled.
1196                 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1197
1198                 // Update the menu checkbox.
1199                 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1200
1201                 // Update the privacy icon.  `true` refreshes the app bar icons.
1202                 updatePrivacyIcons(true);
1203
1204                 // Display a snackbar.
1205                 if (currentWebView.getSettings().getDomStorageEnabled()) {
1206                     Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1207                 } else {
1208                     Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1209                 }
1210
1211                 // Reload the current WebView.
1212                 currentWebView.reload();
1213
1214                 // Consume the event.
1215                 return true;
1216
1217             // Form data can be removed once the minimum API >= 26.
1218             case R.id.toggle_save_form_data:
1219                 // Switch the status of saveFormDataEnabled.
1220                 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1221
1222                 // Update the menu checkbox.
1223                 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1224
1225                 // Display a snackbar.
1226                 if (currentWebView.getSettings().getSaveFormData()) {
1227                     Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1228                 } else {
1229                     Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1230                 }
1231
1232                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1233                 updatePrivacyIcons(true);
1234
1235                 // Reload the current WebView.
1236                 currentWebView.reload();
1237
1238                 // Consume the event.
1239                 return true;
1240
1241             case R.id.clear_cookies:
1242                 Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1243                         .setAction(R.string.undo, v -> {
1244                             // Do nothing because everything will be handled by `onDismissed()` below.
1245                         })
1246                         .addCallback(new Snackbar.Callback() {
1247                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1248                             @Override
1249                             public void onDismissed(Snackbar snackbar, int event) {
1250                                 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1251                                     // Delete the cookies, which command varies by SDK.
1252                                     if (Build.VERSION.SDK_INT < 21) {
1253                                         cookieManager.removeAllCookie();
1254                                     } else {
1255                                         cookieManager.removeAllCookies(null);
1256                                     }
1257                                 }
1258                             }
1259                         })
1260                         .show();
1261
1262                 // Consume the event.
1263                 return true;
1264
1265             case R.id.clear_dom_storage:
1266                 Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1267                         .setAction(R.string.undo, v -> {
1268                             // Do nothing because everything will be handled by `onDismissed()` below.
1269                         })
1270                         .addCallback(new Snackbar.Callback() {
1271                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1272                             @Override
1273                             public void onDismissed(Snackbar snackbar, int event) {
1274                                 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1275                                     // Delete the DOM Storage.
1276                                     WebStorage webStorage = WebStorage.getInstance();
1277                                     webStorage.deleteAllData();
1278
1279                                     // Initialize a handler to manually delete the DOM storage files and directories.
1280                                     Handler deleteDomStorageHandler = new Handler();
1281
1282                                     // Setup a runnable to manually delete the DOM storage files and directories.
1283                                     Runnable deleteDomStorageRunnable = () -> {
1284                                         try {
1285                                             // Get a handle for the runtime.
1286                                             Runtime runtime = Runtime.getRuntime();
1287
1288                                             // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1289                                             // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1290                                             String privateDataDirectoryString = getApplicationInfo().dataDir;
1291
1292                                             // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1293                                             Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1294
1295                                             // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1296                                             Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1297                                             Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1298                                             Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1299                                             Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1300
1301                                             // Wait for the processes to finish.
1302                                             deleteLocalStorageProcess.waitFor();
1303                                             deleteIndexProcess.waitFor();
1304                                             deleteQuotaManagerProcess.waitFor();
1305                                             deleteQuotaManagerJournalProcess.waitFor();
1306                                             deleteDatabasesProcess.waitFor();
1307                                         } catch (Exception exception) {
1308                                             // Do nothing if an error is thrown.
1309                                         }
1310                                     };
1311
1312                                     // Manually delete the DOM storage files after 200 milliseconds.
1313                                     deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1314                                 }
1315                             }
1316                         })
1317                         .show();
1318
1319                 // Consume the event.
1320                 return true;
1321
1322             // Form data can be remove once the minimum API >= 26.
1323             case R.id.clear_form_data:
1324                 Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1325                         .setAction(R.string.undo, v -> {
1326                             // Do nothing because everything will be handled by `onDismissed()` below.
1327                         })
1328                         .addCallback(new Snackbar.Callback() {
1329                             @SuppressLint("SwitchIntDef")  // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1330                             @Override
1331                             public void onDismissed(Snackbar snackbar, int event) {
1332                                 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1333                                     // Delete the form data.
1334                                     WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1335                                     mainWebViewDatabase.clearFormData();
1336                                 }
1337                             }
1338                         })
1339                         .show();
1340
1341                 // Consume the event.
1342                 return true;
1343
1344             case R.id.easylist:
1345                 // Toggle the EasyList status.
1346                 currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1347
1348                 // Update the menu checkbox.
1349                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1350
1351                 // Reload the current WebView.
1352                 currentWebView.reload();
1353
1354                 // Consume the event.
1355                 return true;
1356
1357             case R.id.easyprivacy:
1358                 // Toggle the EasyPrivacy status.
1359                 currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1360
1361                 // Update the menu checkbox.
1362                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1363
1364                 // Reload the current WebView.
1365                 currentWebView.reload();
1366
1367                 // Consume the event.
1368                 return true;
1369
1370             case R.id.fanboys_annoyance_list:
1371                 // Toggle Fanboy's Annoyance List status.
1372                 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1373
1374                 // Update the menu checkbox.
1375                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1376
1377                 // Update the staus of Fanboy's Social Blocking List.
1378                 MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
1379                 fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1380
1381                 // Reload the current WebView.
1382                 currentWebView.reload();
1383
1384                 // Consume the event.
1385                 return true;
1386
1387             case R.id.fanboys_social_blocking_list:
1388                 // Toggle Fanboy's Social Blocking List status.
1389                 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1390
1391                 // Update the menu checkbox.
1392                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1393
1394                 // Reload the current WebView.
1395                 currentWebView.reload();
1396
1397                 // Consume the event.
1398                 return true;
1399
1400             case R.id.ultralist:
1401                 // Toggle the UltraList status.
1402                 currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1403
1404                 // Update the menu checkbox.
1405                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1406
1407                 // Reload the current WebView.
1408                 currentWebView.reload();
1409
1410                 // Consume the event.
1411                 return true;
1412
1413             case R.id.ultraprivacy:
1414                 // Toggle the UltraPrivacy status.
1415                 currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1416
1417                 // Update the menu checkbox.
1418                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1419
1420                 // Reload the current WebView.
1421                 currentWebView.reload();
1422
1423                 // Consume the event.
1424                 return true;
1425
1426             case R.id.block_all_third_party_requests:
1427                 //Toggle the third-party requests blocker status.
1428                 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1429
1430                 // Update the menu checkbox.
1431                 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1432
1433                 // Reload the current WebView.
1434                 currentWebView.reload();
1435
1436                 // Consume the event.
1437                 return true;
1438
1439             case R.id.proxy_none:
1440                 // Update the proxy mode.
1441                 proxyMode = ProxyHelper.NONE;
1442
1443                 // Apply the proxy mode.
1444                 applyProxy(true);
1445
1446                 // Consume the event.
1447                 return true;
1448
1449             case R.id.proxy_tor:
1450                 // Update the proxy mode.
1451                 proxyMode = ProxyHelper.TOR;
1452
1453                 // Apply the proxy mode.
1454                 applyProxy(true);
1455
1456                 // Consume the event.
1457                 return true;
1458
1459             case R.id.proxy_i2p:
1460                 // Update the proxy mode.
1461                 proxyMode = ProxyHelper.I2P;
1462
1463                 // Apply the proxy mode.
1464                 applyProxy(true);
1465
1466                 // Consume the event.
1467                 return true;
1468
1469             case R.id.proxy_custom:
1470                 // Update the proxy mode.
1471                 proxyMode = ProxyHelper.CUSTOM;
1472
1473                 // Apply the proxy mode.
1474                 applyProxy(true);
1475
1476                 // Consume the event.
1477                 return true;
1478
1479             case R.id.user_agent_privacy_browser:
1480                 // Update the user agent.
1481                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1482
1483                 // Reload the current WebView.
1484                 currentWebView.reload();
1485
1486                 // Consume the event.
1487                 return true;
1488
1489             case R.id.user_agent_webview_default:
1490                 // Update the user agent.
1491                 currentWebView.getSettings().setUserAgentString("");
1492
1493                 // Reload the current WebView.
1494                 currentWebView.reload();
1495
1496                 // Consume the event.
1497                 return true;
1498
1499             case R.id.user_agent_firefox_on_android:
1500                 // Update the user agent.
1501                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1502
1503                 // Reload the current WebView.
1504                 currentWebView.reload();
1505
1506                 // Consume the event.
1507                 return true;
1508
1509             case R.id.user_agent_chrome_on_android:
1510                 // Update the user agent.
1511                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1512
1513                 // Reload the current WebView.
1514                 currentWebView.reload();
1515
1516                 // Consume the event.
1517                 return true;
1518
1519             case R.id.user_agent_safari_on_ios:
1520                 // Update the user agent.
1521                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1522
1523                 // Reload the current WebView.
1524                 currentWebView.reload();
1525
1526                 // Consume the event.
1527                 return true;
1528
1529             case R.id.user_agent_firefox_on_linux:
1530                 // Update the user agent.
1531                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1532
1533                 // Reload the current WebView.
1534                 currentWebView.reload();
1535
1536                 // Consume the event.
1537                 return true;
1538
1539             case R.id.user_agent_chromium_on_linux:
1540                 // Update the user agent.
1541                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1542
1543                 // Reload the current WebView.
1544                 currentWebView.reload();
1545
1546                 // Consume the event.
1547                 return true;
1548
1549             case R.id.user_agent_firefox_on_windows:
1550                 // Update the user agent.
1551                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1552
1553                 // Reload the current WebView.
1554                 currentWebView.reload();
1555
1556                 // Consume the event.
1557                 return true;
1558
1559             case R.id.user_agent_chrome_on_windows:
1560                 // Update the user agent.
1561                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1562
1563                 // Reload the current WebView.
1564                 currentWebView.reload();
1565
1566                 // Consume the event.
1567                 return true;
1568
1569             case R.id.user_agent_edge_on_windows:
1570                 // Update the user agent.
1571                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1572
1573                 // Reload the current WebView.
1574                 currentWebView.reload();
1575
1576                 // Consume the event.
1577                 return true;
1578
1579             case R.id.user_agent_internet_explorer_on_windows:
1580                 // Update the user agent.
1581                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1582
1583                 // Reload the current WebView.
1584                 currentWebView.reload();
1585
1586                 // Consume the event.
1587                 return true;
1588
1589             case R.id.user_agent_safari_on_macos:
1590                 // Update the user agent.
1591                 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1592
1593                 // Reload the current WebView.
1594                 currentWebView.reload();
1595
1596                 // Consume the event.
1597                 return true;
1598
1599             case R.id.user_agent_custom:
1600                 // Update the user agent.
1601                 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1602
1603                 // Reload the current WebView.
1604                 currentWebView.reload();
1605
1606                 // Consume the event.
1607                 return true;
1608
1609             case R.id.font_size:
1610                 // Instantiate the font size dialog.
1611                 DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
1612
1613                 // Show the font size dialog.
1614                 fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
1615
1616                 // Consume the event.
1617                 return true;
1618
1619             case R.id.swipe_to_refresh:
1620                 // Toggle the stored status of swipe to refresh.
1621                 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1622
1623                 // Get a handle for the swipe refresh layout.
1624                 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
1625
1626                 // Update the swipe refresh layout.
1627                 if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
1628                     // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
1629                     swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
1630                 } else {  // Swipe to refresh is disabled.
1631                     // Disable the swipe refresh layout.
1632                     swipeRefreshLayout.setEnabled(false);
1633                 }
1634
1635                 // Consume the event.
1636                 return true;
1637
1638             case R.id.wide_viewport:
1639                 // Toggle the viewport.
1640                 currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1641
1642                 // Consume the event.
1643                 return true;
1644
1645             case R.id.display_images:
1646                 if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
1647                     // Disable loading of images.
1648                     currentWebView.getSettings().setLoadsImagesAutomatically(false);
1649
1650                     // Reload the website to remove existing images.
1651                     currentWebView.reload();
1652                 } else {  // Images are not currently loaded automatically.
1653                     // Enable loading of images.  Missing images will be loaded without the need for a reload.
1654                     currentWebView.getSettings().setLoadsImagesAutomatically(true);
1655                 }
1656
1657                 // Consume the event.
1658                 return true;
1659
1660             case R.id.dark_webview:
1661                 // Check to see if dark WebView is supported by this WebView.
1662                 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
1663                     // Toggle the dark WebView setting.
1664                     if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) {  // Dark WebView is currently enabled.
1665                         // Turn off dark WebView.
1666                         WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
1667                     } else {  // Dark WebView is currently disabled.
1668                         // turn on dark WebView.
1669                         WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
1670                     }
1671                 }
1672
1673                 // Consume the event.
1674                 return true;
1675
1676             case R.id.find_on_page:
1677                 // Get a handle for the views.
1678                 Toolbar toolbar = findViewById(R.id.toolbar);
1679                 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1680                 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1681
1682                 // Set the minimum height of the find on page linear layout to match the toolbar.
1683                 findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1684
1685                 // Hide the toolbar.
1686                 toolbar.setVisibility(View.GONE);
1687
1688                 // Show the find on page linear layout.
1689                 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1690
1691                 // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
1692                 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1693                 findOnPageEditText.postDelayed(() -> {
1694                     // Set the focus on `findOnPageEditText`.
1695                     findOnPageEditText.requestFocus();
1696
1697                     // Get a handle for the input method manager.
1698                     InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1699
1700                     // Remove the lint warning below that the input method manager might be null.
1701                     assert inputMethodManager != null;
1702
1703                     // Display the keyboard.  `0` sets no input flags.
1704                     inputMethodManager.showSoftInput(findOnPageEditText, 0);
1705                 }, 200);
1706
1707                 // Consume the event.
1708                 return true;
1709
1710             case R.id.print:
1711                 // Get a print manager instance.
1712                 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1713
1714                 // Remove the lint error below that print manager might be null.
1715                 assert printManager != null;
1716
1717                 // Create a print document adapter from the current WebView.
1718                 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1719
1720                 // Print the document.
1721                 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
1722
1723                 // Consume the event.
1724                 return true;
1725
1726             case R.id.save_url:
1727                 // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
1728                 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
1729                         currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
1730
1731                 // Consume the event.
1732                 return true;
1733
1734             case R.id.save_as_archive:
1735                 // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
1736                 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getSettings().getUserAgentString(),
1737                         currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
1738
1739                 // Consume the event.
1740                 return true;
1741
1742             case R.id.save_as_image:
1743                 // Prepare the save dialog.  The dialog will be displayed once the file size adn the content disposition have been acquired.
1744                 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getSettings().getUserAgentString(),
1745                         currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
1746
1747                 // Consume the event.
1748                 return true;
1749
1750             case R.id.add_to_homescreen:
1751                 // Instantiate the create home screen shortcut dialog.
1752                 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1753                         currentWebView.getFavoriteOrDefaultIcon());
1754
1755                 // Show the create home screen shortcut dialog.
1756                 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1757
1758                 // Consume the event.
1759                 return true;
1760
1761             case R.id.view_source:
1762                 // Create an intent to launch the view source activity.
1763                 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1764
1765                 // Add the variables to the intent.
1766                 viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
1767                 viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
1768
1769                 // Make it so.
1770                 startActivity(viewSourceIntent);
1771
1772                 // Consume the event.
1773                 return true;
1774
1775             case R.id.share_url:
1776                 // Setup the share string.
1777                 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1778
1779                 // Create the share intent.
1780                 Intent shareIntent = new Intent(Intent.ACTION_SEND);
1781                 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1782                 shareIntent.setType("text/plain");
1783
1784                 // Make it so.
1785                 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1786
1787                 // Consume the event.
1788                 return true;
1789
1790             case R.id.open_with_app:
1791                 // Open the URL with an outside app.
1792                 openWithApp(currentWebView.getUrl());
1793
1794                 // Consume the event.
1795                 return true;
1796
1797             case R.id.open_with_browser:
1798                 // Open the URL with an outside browser.
1799                 openWithBrowser(currentWebView.getUrl());
1800
1801                 // Consume the event.
1802                 return true;
1803
1804             case R.id.add_or_edit_domain:
1805                 if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
1806                     // Reapply the domain settings on returning to `MainWebViewActivity`.
1807                     reapplyDomainSettingsOnRestart = true;
1808
1809                     // Create an intent to launch the domains activity.
1810                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
1811
1812                     // Add the extra information to the intent.
1813                     domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1814                     domainsIntent.putExtra("close_on_back", true);
1815                     domainsIntent.putExtra("current_url", currentWebView.getUrl());
1816
1817                     // Get the current certificate.
1818                     SslCertificate sslCertificate = currentWebView.getCertificate();
1819
1820                     // Check to see if the SSL certificate is populated.
1821                     if (sslCertificate != null) {
1822                         // Extract the certificate to strings.
1823                         String issuedToCName = sslCertificate.getIssuedTo().getCName();
1824                         String issuedToOName = sslCertificate.getIssuedTo().getOName();
1825                         String issuedToUName = sslCertificate.getIssuedTo().getUName();
1826                         String issuedByCName = sslCertificate.getIssuedBy().getCName();
1827                         String issuedByOName = sslCertificate.getIssuedBy().getOName();
1828                         String issuedByUName = sslCertificate.getIssuedBy().getUName();
1829                         long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1830                         long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1831
1832                         // Add the certificate to the intent.
1833                         domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1834                         domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1835                         domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1836                         domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1837                         domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1838                         domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1839                         domainsIntent.putExtra("ssl_start_date", startDateLong);
1840                         domainsIntent.putExtra("ssl_end_date", endDateLong);
1841                     }
1842
1843                     // Check to see if the current IP addresses have been received.
1844                     if (currentWebView.hasCurrentIpAddresses()) {
1845                         // Add the current IP addresses to the intent.
1846                         domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1847                     }
1848
1849                     // Make it so.
1850                     startActivity(domainsIntent);
1851                 } else {  // Add a new domain.
1852                     // Apply the new domain settings on returning to `MainWebViewActivity`.
1853                     reapplyDomainSettingsOnRestart = true;
1854
1855                     // Get the current domain
1856                     Uri currentUri = Uri.parse(currentWebView.getUrl());
1857                     String currentDomain = currentUri.getHost();
1858
1859                     // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1860                     DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1861
1862                     // Create the domain and store the database ID.
1863                     int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1864
1865                     // Create an intent to launch the domains activity.
1866                     Intent domainsIntent = new Intent(this, DomainsActivity.class);
1867
1868                     // Add the extra information to the intent.
1869                     domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1870                     domainsIntent.putExtra("close_on_back", true);
1871                     domainsIntent.putExtra("current_url", currentWebView.getUrl());
1872
1873                     // Get the current certificate.
1874                     SslCertificate sslCertificate = currentWebView.getCertificate();
1875
1876                     // Check to see if the SSL certificate is populated.
1877                     if (sslCertificate != null) {
1878                         // Extract the certificate to strings.
1879                         String issuedToCName = sslCertificate.getIssuedTo().getCName();
1880                         String issuedToOName = sslCertificate.getIssuedTo().getOName();
1881                         String issuedToUName = sslCertificate.getIssuedTo().getUName();
1882                         String issuedByCName = sslCertificate.getIssuedBy().getCName();
1883                         String issuedByOName = sslCertificate.getIssuedBy().getOName();
1884                         String issuedByUName = sslCertificate.getIssuedBy().getUName();
1885                         long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1886                         long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1887
1888                         // Add the certificate to the intent.
1889                         domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1890                         domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1891                         domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1892                         domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1893                         domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1894                         domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1895                         domainsIntent.putExtra("ssl_start_date", startDateLong);
1896                         domainsIntent.putExtra("ssl_end_date", endDateLong);
1897                     }
1898
1899                     // Check to see if the current IP addresses have been received.
1900                     if (currentWebView.hasCurrentIpAddresses()) {
1901                         // Add the current IP addresses to the intent.
1902                         domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1903                     }
1904
1905                     // Make it so.
1906                     startActivity(domainsIntent);
1907                 }
1908
1909                 // Consume the event.
1910                 return true;
1911
1912             case R.id.ad_consent:
1913                 // Instantiate the ad consent dialog.
1914                 DialogFragment adConsentDialogFragment = new AdConsentDialog();
1915
1916                 // Display the ad consent dialog.
1917                 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
1918
1919                 // Consume the event.
1920                 return true;
1921
1922             default:
1923                 // Don't consume the event.
1924                 return super.onOptionsItemSelected(menuItem);
1925         }
1926     }
1927
1928     // removeAllCookies is deprecated, but it is required for API < 21.
1929     @Override
1930     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1931         // Get the menu item ID.
1932         int menuItemId = menuItem.getItemId();
1933
1934         // Get a handle for the shared preferences.
1935         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1936
1937         // Run the commands that correspond to the selected menu item.
1938         switch (menuItemId) {
1939             case R.id.clear_and_exit:
1940                 // Clear and exit Privacy Browser.
1941                 clearAndExit();
1942                 break;
1943
1944             case R.id.home:
1945                 // Load the homepage.
1946                 loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
1947                 break;
1948
1949             case R.id.back:
1950                 if (currentWebView.canGoBack()) {
1951                     // Get the current web back forward list.
1952                     WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1953
1954                     // Get the previous entry URL.
1955                     String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
1956
1957                     // Apply the domain settings.
1958                     applyDomainSettings(currentWebView, previousUrl, false, false);
1959
1960                     // Load the previous website in the history.
1961                     currentWebView.goBack();
1962                 }
1963                 break;
1964
1965             case R.id.forward:
1966                 if (currentWebView.canGoForward()) {
1967                     // Get the current web back forward list.
1968                     WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1969
1970                     // Get the next entry URL.
1971                     String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
1972
1973                     // Apply the domain settings.
1974                     applyDomainSettings(currentWebView, nextUrl, false, false);
1975
1976                     // Load the next website in the history.
1977                     currentWebView.goForward();
1978                 }
1979                 break;
1980
1981             case R.id.history:
1982                 // Instantiate the URL history dialog.
1983                 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
1984
1985                 // Show the URL history dialog.
1986                 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
1987                 break;
1988
1989             case R.id.open:
1990                 // Instantiate the open file dialog.
1991                 DialogFragment openDialogFragment = new OpenDialog();
1992
1993                 // Show the open file dialog.
1994                 openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
1995                 break;
1996
1997             case R.id.requests:
1998                 // Populate the resource requests.
1999                 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
2000
2001                 // Create an intent to launch the Requests activity.
2002                 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2003
2004                 // Add the block third-party requests status to the intent.
2005                 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
2006
2007                 // Make it so.
2008                 startActivity(requestsIntent);
2009                 break;
2010
2011             case R.id.downloads:
2012                 // Launch the system Download Manager.
2013                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2014
2015                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
2016                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2017
2018                 // Make it so.
2019                 startActivity(downloadManagerIntent);
2020                 break;
2021
2022             case R.id.domains:
2023                 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2024                 reapplyDomainSettingsOnRestart = true;
2025
2026                 // Launch the domains activity.
2027                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2028
2029                 // Add the extra information to the intent.
2030                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
2031
2032                 // Get the current certificate.
2033                 SslCertificate sslCertificate = currentWebView.getCertificate();
2034
2035                 // Check to see if the SSL certificate is populated.
2036                 if (sslCertificate != null) {
2037                     // Extract the certificate to strings.
2038                     String issuedToCName = sslCertificate.getIssuedTo().getCName();
2039                     String issuedToOName = sslCertificate.getIssuedTo().getOName();
2040                     String issuedToUName = sslCertificate.getIssuedTo().getUName();
2041                     String issuedByCName = sslCertificate.getIssuedBy().getCName();
2042                     String issuedByOName = sslCertificate.getIssuedBy().getOName();
2043                     String issuedByUName = sslCertificate.getIssuedBy().getUName();
2044                     long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2045                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2046
2047                     // Add the certificate to the intent.
2048                     domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2049                     domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2050                     domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2051                     domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2052                     domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2053                     domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2054                     domainsIntent.putExtra("ssl_start_date", startDateLong);
2055                     domainsIntent.putExtra("ssl_end_date", endDateLong);
2056                 }
2057
2058                 // Check to see if the current IP addresses have been received.
2059                 if (currentWebView.hasCurrentIpAddresses()) {
2060                     // Add the current IP addresses to the intent.
2061                     domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2062                 }
2063
2064                 // Make it so.
2065                 startActivity(domainsIntent);
2066                 break;
2067
2068             case R.id.settings:
2069                 // Set the flag to reapply app settings on restart when returning from Settings.
2070                 reapplyAppSettingsOnRestart = true;
2071
2072                 // Set the flag to reapply the domain settings on restart when returning from Settings.
2073                 reapplyDomainSettingsOnRestart = true;
2074
2075                 // Launch the settings activity.
2076                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
2077                 startActivity(settingsIntent);
2078                 break;
2079
2080             case R.id.import_export:
2081                 // Launch the import/export activity.
2082                 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
2083                 startActivity(importExportIntent);
2084                 break;
2085
2086             case R.id.logcat:
2087                 // Launch the logcat activity.
2088                 Intent logcatIntent = new Intent(this, LogcatActivity.class);
2089                 startActivity(logcatIntent);
2090                 break;
2091
2092             case R.id.guide:
2093                 // Launch `GuideActivity`.
2094                 Intent guideIntent = new Intent(this, GuideActivity.class);
2095                 startActivity(guideIntent);
2096                 break;
2097
2098             case R.id.about:
2099                 // Create an intent to launch the about activity.
2100                 Intent aboutIntent = new Intent(this, AboutActivity.class);
2101
2102                 // Create a string array for the blocklist versions.
2103                 String[] blocklistVersions = new String[] {easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0],
2104                         ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
2105
2106                 // Add the blocklist versions to the intent.
2107                 aboutIntent.putExtra("blocklist_versions", blocklistVersions);
2108
2109                 // Make it so.
2110                 startActivity(aboutIntent);
2111                 break;
2112         }
2113
2114         // Get a handle for the drawer layout.
2115         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2116
2117         // Close the navigation drawer.
2118         drawerLayout.closeDrawer(GravityCompat.START);
2119         return true;
2120     }
2121
2122     @Override
2123     public void onPostCreate(Bundle savedInstanceState) {
2124         // Run the default commands.
2125         super.onPostCreate(savedInstanceState);
2126
2127         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
2128         actionBarDrawerToggle.syncState();
2129     }
2130
2131     @Override
2132     public void onConfigurationChanged(@NonNull Configuration newConfig) {
2133         // Run the default commands.
2134         super.onConfigurationChanged(newConfig);
2135
2136         // Reload the ad for the free flavor if not in full screen mode.
2137         if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2138             // Reload the ad.  The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2139             AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
2140         }
2141
2142         // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
2143         // https://code.google.com/p/android/issues/detail?id=20493#c8
2144         // ActivityCompat.invalidateOptionsMenu(this);
2145     }
2146
2147     @Override
2148     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2149         // Store the hit test result.
2150         final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2151
2152         // Define the URL strings.
2153         final String imageUrl;
2154         final String linkUrl;
2155
2156         // Get handles for the system managers.
2157         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2158
2159         // Remove the lint errors below that the clipboard manager might be null.
2160         assert clipboardManager != null;
2161
2162         // Process the link according to the type.
2163         switch (hitTestResult.getType()) {
2164             // `SRC_ANCHOR_TYPE` is a link.
2165             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2166                 // Get the target URL.
2167                 linkUrl = hitTestResult.getExtra();
2168
2169                 // Set the target URL as the title of the `ContextMenu`.
2170                 menu.setHeaderTitle(linkUrl);
2171
2172                 // Add an Open in New Tab entry.
2173                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2174                     // Load the link URL in a new tab and move to it.
2175                     addNewTab(linkUrl, true);
2176
2177                     // Consume the event.
2178                     return true;
2179                 });
2180
2181                 // Add an Open in Background entry.
2182                 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2183                     // Load the link URL in a new tab but do not move to it.
2184                     addNewTab(linkUrl, false);
2185
2186                     // Consume the event.
2187                     return true;
2188                 });
2189
2190                 // Add an Open with App entry.
2191                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2192                     openWithApp(linkUrl);
2193
2194                     // Consume the event.
2195                     return true;
2196                 });
2197
2198                 // Add an Open with Browser entry.
2199                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2200                     openWithBrowser(linkUrl);
2201
2202                     // Consume the event.
2203                     return true;
2204                 });
2205
2206                 // Add a Copy URL entry.
2207                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2208                     // Save the link URL in a `ClipData`.
2209                     ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2210
2211                     // Set the `ClipData` as the clipboard's primary clip.
2212                     clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2213
2214                     // Consume the event.
2215                     return true;
2216                 });
2217
2218                 // Add a Save URL entry.
2219                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2220                     // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2221                     new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2222                             currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
2223
2224                     // Consume the event.
2225                     return true;
2226                 });
2227
2228                 // Add an empty Cancel entry, which by default closes the context menu.
2229                 menu.add(R.string.cancel);
2230                 break;
2231
2232             // `IMAGE_TYPE` is an image.
2233             case WebView.HitTestResult.IMAGE_TYPE:
2234                 // Get the image URL.
2235                 imageUrl = hitTestResult.getExtra();
2236
2237                 // Set the image URL as the title of the context menu.
2238                 menu.setHeaderTitle(imageUrl);
2239
2240                 // Add an Open in New Tab entry.
2241                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2242                     // Load the image in a new tab.
2243                     addNewTab(imageUrl, true);
2244
2245                     // Consume the event.
2246                     return true;
2247                 });
2248
2249                 // Add an Open with App entry.
2250                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2251                     // Open the image URL with an external app.
2252                     openWithApp(imageUrl);
2253
2254                     // Consume the event.
2255                     return true;
2256                 });
2257
2258                 // Add an Open with Browser entry.
2259                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2260                     // Open the image URL with an external browser.
2261                     openWithBrowser(imageUrl);
2262
2263                     // Consume the event.
2264                     return true;
2265                 });
2266
2267                 // Add a View Image entry.
2268                 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2269                     // Load the image in the current tab.
2270                     loadUrl(currentWebView, imageUrl);
2271
2272                     // Consume the event.
2273                     return true;
2274                 });
2275
2276                 // Add a Save Image entry.
2277                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2278                    // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2279                     new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2280                             currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
2281
2282                     // Consume the event.
2283                     return true;
2284                 });
2285
2286                 // Add a Copy URL entry.
2287                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2288                     // Save the image URL in a clip data.
2289                     ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2290
2291                     // Set the clip data as the clipboard's primary clip.
2292                     clipboardManager.setPrimaryClip(imageTypeClipData);
2293
2294                     // Consume the event.
2295                     return true;
2296                 });
2297
2298                 // Add an empty Cancel entry, which by default closes the context menu.
2299                 menu.add(R.string.cancel);
2300                 break;
2301
2302             // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2303             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2304                 // Get the image URL.
2305                 imageUrl = hitTestResult.getExtra();
2306
2307                 // Instantiate a handler.
2308                 Handler handler = new Handler();
2309
2310                 // Get a message from the handler.
2311                 Message message = handler.obtainMessage();
2312
2313                 // Request the image details from the last touched node be returned in the message.
2314                 currentWebView.requestFocusNodeHref(message);
2315
2316                 // Get the link URL from the message data.
2317                 linkUrl = message.getData().getString("url");
2318
2319                 // Set the link URL as the title of the context menu.
2320                 menu.setHeaderTitle(linkUrl);
2321
2322                 // Add an Open in New Tab entry.
2323                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2324                     // Load the link URL in a new tab and move to it.
2325                     addNewTab(linkUrl, true);
2326
2327                     // Consume the event.
2328                     return true;
2329                 });
2330
2331                 // Add an Open in Background entry.
2332                 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2333                     // Lod the link URL in a new tab but do not move to it.
2334                     addNewTab(linkUrl, false);
2335
2336                     // Consume the event.
2337                     return true;
2338                 });
2339
2340                 // Add an Open Image in New Tab entry.
2341                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2342                     // Load the image in a new tab and move to it.
2343                     addNewTab(imageUrl, true);
2344
2345                     // Consume the event.
2346                     return true;
2347                 });
2348
2349                 // Add an Open with App entry.
2350                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2351                     // Open the link URL with an external app.
2352                     openWithApp(linkUrl);
2353
2354                     // Consume the event.
2355                     return true;
2356                 });
2357
2358                 // Add an Open with Browser entry.
2359                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2360                     // Open the link URL with an external browser.
2361                     openWithBrowser(linkUrl);
2362
2363                     // Consume the event.
2364                     return true;
2365                 });
2366
2367                 // Add a View Image entry.
2368                 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2369                    // View the image in the current tab.
2370                    loadUrl(currentWebView, imageUrl);
2371
2372                    // Consume the event.
2373                    return true;
2374                 });
2375
2376                 // Add a Save Image entry.
2377                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2378                     // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2379                     new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2380                             currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
2381
2382                     // Consume the event.
2383                     return true;
2384                 });
2385
2386                 // Add a Copy URL entry.
2387                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2388                     // Save the link URL in a clip data.
2389                     ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2390
2391                     // Set the clip data as the clipboard's primary clip.
2392                     clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2393
2394                     // Consume the event.
2395                     return true;
2396                 });
2397
2398                 // Add a Save URL entry.
2399                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2400                     // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2401                     new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2402                             currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
2403
2404                     // Consume the event.
2405                     return true;
2406                 });
2407
2408                 // Add an empty Cancel entry, which by default closes the context menu.
2409                 menu.add(R.string.cancel);
2410                 break;
2411
2412             case WebView.HitTestResult.EMAIL_TYPE:
2413                 // Get the target URL.
2414                 linkUrl = hitTestResult.getExtra();
2415
2416                 // Set the target URL as the title of the `ContextMenu`.
2417                 menu.setHeaderTitle(linkUrl);
2418
2419                 // Add a Write Email entry.
2420                 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2421                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2422                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2423
2424                     // Parse the url and set it as the data for the `Intent`.
2425                     emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2426
2427                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2428                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2429
2430                     // Make it so.
2431                     startActivity(emailIntent);
2432
2433                     // Consume the event.
2434                     return true;
2435                 });
2436
2437                 // Add a Copy Email Address entry.
2438                 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2439                     // Save the email address in a `ClipData`.
2440                     ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2441
2442                     // Set the `ClipData` as the clipboard's primary clip.
2443                     clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2444
2445                     // Consume the event.
2446                     return true;
2447                 });
2448
2449                 // Add an empty Cancel entry, which by default closes the context menu.
2450                 menu.add(R.string.cancel);
2451                 break;
2452         }
2453     }
2454
2455     @Override
2456     public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2457         // Get a handle for the bookmarks list view.
2458         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2459
2460         // Get the dialog.
2461         Dialog dialog = dialogFragment.getDialog();
2462
2463         // Remove the incorrect lint warning below that the dialog might be null.
2464         assert dialog != null;
2465
2466         // Get the views from the dialog fragment.
2467         EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2468         EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2469
2470         // Extract the strings from the edit texts.
2471         String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2472         String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2473
2474         // Create a favorite icon byte array output stream.
2475         ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2476
2477         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2478         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2479
2480         // Convert the favorite icon byte array stream to a byte array.
2481         byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2482
2483         // Display the new bookmark below the current items in the (0 indexed) list.
2484         int newBookmarkDisplayOrder = bookmarksListView.getCount();
2485
2486         // Create the bookmark.
2487         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2488
2489         // Update the bookmarks cursor with the current contents of this folder.
2490         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2491
2492         // Update the list view.
2493         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2494
2495         // Scroll to the new bookmark.
2496         bookmarksListView.setSelection(newBookmarkDisplayOrder);
2497     }
2498
2499     @Override
2500     public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
2501         // Get a handle for the bookmarks list view.
2502         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2503
2504         // Get the dialog.
2505         Dialog dialog = dialogFragment.getDialog();
2506
2507         // Remove the incorrect lint warning below that the dialog might be null.
2508         assert dialog != null;
2509
2510         // Get handles for the views in the dialog fragment.
2511         EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
2512         RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
2513         ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
2514
2515         // Get new folder name string.
2516         String folderNameString = createFolderNameEditText.getText().toString();
2517
2518         // Create a folder icon bitmap.
2519         Bitmap folderIconBitmap;
2520
2521         // Set the folder icon bitmap according to the dialog.
2522         if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
2523             // Get the default folder icon drawable.
2524             Drawable folderIconDrawable = folderIconImageView.getDrawable();
2525
2526             // Convert the folder icon drawable to a bitmap drawable.
2527             BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2528
2529             // Convert the folder icon bitmap drawable to a bitmap.
2530             folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2531         } else {  // Use the WebView favorite icon.
2532             // Copy the favorite icon bitmap to the folder icon bitmap.
2533             folderIconBitmap = favoriteIconBitmap;
2534         }
2535
2536         // Create a folder icon byte array output stream.
2537         ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2538
2539         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2540         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2541
2542         // Convert the folder icon byte array stream to a byte array.
2543         byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2544
2545         // Move all the bookmarks down one in the display order.
2546         for (int i = 0; i < bookmarksListView.getCount(); i++) {
2547             int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2548             bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2549         }
2550
2551         // Create the folder, which will be placed at the top of the `ListView`.
2552         bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2553
2554         // Update the bookmarks cursor with the current contents of this folder.
2555         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2556
2557         // Update the `ListView`.
2558         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2559
2560         // Scroll to the new folder.
2561         bookmarksListView.setSelection(0);
2562     }
2563
2564     @Override
2565     public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
2566         // Get the dialog.
2567         Dialog dialog = dialogFragment.getDialog();
2568
2569         // Remove the incorrect lint warning below that the dialog might be null.
2570         assert dialog != null;
2571
2572         // Get handles for the views from `dialogFragment`.
2573         EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
2574         RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
2575         RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
2576         ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
2577
2578         // Get the new folder name.
2579         String newFolderNameString = editFolderNameEditText.getText().toString();
2580
2581         // Check if the favorite icon has changed.
2582         if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
2583             // Update the name in the database.
2584             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2585         } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
2586             // Create the new folder icon Bitmap.
2587             Bitmap folderIconBitmap;
2588
2589             // Populate the new folder icon bitmap.
2590             if (defaultFolderIconRadioButton.isChecked()) {
2591                 // Get the default folder icon drawable.
2592                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2593
2594                 // Convert the folder icon drawable to a bitmap drawable.
2595                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2596
2597                 // Convert the folder icon bitmap drawable to a bitmap.
2598                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2599             } else {  // Use the `WebView` favorite icon.
2600                 // Copy the favorite icon bitmap to the folder icon bitmap.
2601                 folderIconBitmap = favoriteIconBitmap;
2602             }
2603
2604             // Create a folder icon byte array output stream.
2605             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2606
2607             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2608             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2609
2610             // Convert the folder icon byte array stream to a byte array.
2611             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2612
2613             // Update the folder icon in the database.
2614             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2615         } else {  // The folder icon and the name have changed.
2616             // Get the new folder icon `Bitmap`.
2617             Bitmap folderIconBitmap;
2618             if (defaultFolderIconRadioButton.isChecked()) {
2619                 // Get the default folder icon drawable.
2620                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2621
2622                 // Convert the folder icon drawable to a bitmap drawable.
2623                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2624
2625                 // Convert the folder icon bitmap drawable to a bitmap.
2626                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2627             } else {  // Use the `WebView` favorite icon.
2628                 // Copy the favorite icon bitmap to the folder icon bitmap.
2629                 folderIconBitmap = favoriteIconBitmap;
2630             }
2631
2632             // Create a folder icon byte array output stream.
2633             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2634
2635             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2636             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2637
2638             // Convert the folder icon byte array stream to a byte array.
2639             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2640
2641             // Update the folder name and icon in the database.
2642             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2643         }
2644
2645         // Update the bookmarks cursor with the current contents of this folder.
2646         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2647
2648         // Update the `ListView`.
2649         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2650     }
2651
2652     // Override `onBackPressed` to handle the navigation drawer and and the WebViews.
2653     @Override
2654     public void onBackPressed() {
2655         // Get a handle for the drawer layout.
2656         DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2657
2658         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
2659             // Close the navigation drawer.
2660             drawerLayout.closeDrawer(GravityCompat.START);
2661         } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
2662             if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
2663                 // close the bookmarks drawer.
2664                 drawerLayout.closeDrawer(GravityCompat.END);
2665             } else {  // A subfolder is displayed.
2666                 // Place the former parent folder in `currentFolder`.
2667                 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
2668
2669                 // Load the new folder.
2670                 loadBookmarksFolder();
2671             }
2672         } else if (displayingFullScreenVideo) {  // A full screen video is shown.
2673             // Get a handle for the layouts.
2674             FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
2675             RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
2676             FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
2677
2678             // Re-enable the screen timeout.
2679             fullScreenVideoFrameLayout.setKeepScreenOn(false);
2680
2681             // Unset the full screen video flag.
2682             displayingFullScreenVideo = false;
2683
2684             // Remove all the views from the full screen video frame layout.
2685             fullScreenVideoFrameLayout.removeAllViews();
2686
2687             // Hide the full screen video frame layout.
2688             fullScreenVideoFrameLayout.setVisibility(View.GONE);
2689
2690             // Enable the sliding drawers.
2691             drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
2692
2693             // Show the main content relative layout.
2694             mainContentRelativeLayout.setVisibility(View.VISIBLE);
2695
2696             // Apply the appropriate full screen mode flags.
2697             if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
2698                 // Hide the banner ad in the free flavor.
2699                 if (BuildConfig.FLAVOR.contentEquals("free")) {
2700                     AdHelper.hideAd(findViewById(R.id.adview));
2701                 }
2702
2703                 /* Hide the system bars.
2704                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2705                  * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
2706                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2707                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2708                  */
2709                 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
2710                         View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2711
2712                 // Reload the website if the app bar is hidden.  Otherwise, there is some bug in Android that causes the WebView to be entirely black.
2713                 if (hideAppBar) {
2714                     // Reload the WebView.
2715                     currentWebView.reload();
2716                 }
2717             } else {  // Switch to normal viewing mode.
2718                 // Remove the `SYSTEM_UI` flags from the root frame layout.
2719                 rootFrameLayout.setSystemUiVisibility(0);
2720             }
2721
2722             // Reload the ad for the free flavor if not in full screen mode.
2723             if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2724                 // Reload the ad.
2725                 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
2726             }
2727         } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
2728             // Get the current web back forward list.
2729             WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2730
2731             // Get the previous entry URL.
2732             String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2733
2734             // Apply the domain settings.
2735             applyDomainSettings(currentWebView, previousUrl, false, false);
2736
2737             // Go back.
2738             currentWebView.goBack();
2739         } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
2740             // Close the current tab.
2741             closeCurrentTab();
2742         } else {  // There isn't anything to do in Privacy Browser.
2743             // Close Privacy Browser.  `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
2744             if (Build.VERSION.SDK_INT >= 21) {
2745                 finishAndRemoveTask();
2746             } else {
2747                 finish();
2748             }
2749
2750             // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
2751             System.exit(0);
2752         }
2753     }
2754
2755     // Process the results of a file browse.
2756     @Override
2757     public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
2758         // Run the default commands.
2759         super.onActivityResult(requestCode, resultCode, returnedIntent);
2760
2761         // Run the commands that correlate to the specified request code.
2762         switch (requestCode) {
2763             case BROWSE_FILE_UPLOAD_REQUEST_CODE:
2764                 // File uploads only work on API >= 21.
2765                 if (Build.VERSION.SDK_INT >= 21) {
2766                     // Pass the file to the WebView.
2767                     fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
2768                 }
2769                 break;
2770
2771             case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
2772                 // Don't do anything if the user pressed back from the file picker.
2773                 if (resultCode == Activity.RESULT_OK) {
2774                     // Get a handle for the save dialog fragment.
2775                     DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
2776
2777                     // Only update the file name if the dialog still exists.
2778                     if (saveWebpageDialogFragment != null) {
2779                         // Get a handle for the save webpage dialog.
2780                         Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
2781
2782                         // Remove the incorrect lint warning below that the dialog might be null.
2783                         assert saveWebpageDialog != null;
2784
2785                         // Get a handle for the file name edit text.
2786                         EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
2787                         TextView fileExistsWarningTextView = saveWebpageDialog.findViewById(R.id.file_exists_warning_textview);
2788
2789                         // Instantiate the file name helper.
2790                         FileNameHelper fileNameHelper = new FileNameHelper();
2791
2792                         // Get the file path if it isn't null.
2793                         if (returnedIntent.getData() != null) {
2794                             // Convert the file name URI to a file name path.
2795                             String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
2796
2797                             // Set the file name path as the text of the file name edit text.
2798                             fileNameEditText.setText(fileNamePath);
2799
2800                             // Move the cursor to the end of the file name edit text.
2801                             fileNameEditText.setSelection(fileNamePath.length());
2802
2803                             // Hide the file exists warning.
2804                             fileExistsWarningTextView.setVisibility(View.GONE);
2805                         }
2806                     }
2807                 }
2808                 break;
2809
2810             case BROWSE_OPEN_REQUEST_CODE:
2811                 // Don't do anything if the user pressed back from the file picker.
2812                 if (resultCode == Activity.RESULT_OK) {
2813                     // Get a handle for the open dialog fragment.
2814                     DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
2815
2816                     // Only update the file name if the dialog still exists.
2817                     if (openDialogFragment != null) {
2818                         // Get a handle for the open dialog.
2819                         Dialog openDialog = openDialogFragment.getDialog();
2820
2821                         // Remove the incorrect lint warning below that the dialog might be null.
2822                         assert openDialog != null;
2823
2824                         // Get a handle for the file name edit text.
2825                         EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
2826
2827                         // Instantiate the file name helper.
2828                         FileNameHelper fileNameHelper = new FileNameHelper();
2829
2830                         // Get the file path if it isn't null.
2831                         if (returnedIntent.getData() != null) {
2832                             // Convert the file name URI to a file name path.
2833                             String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
2834
2835                             // Set the file name path as the text of the file name edit text.
2836                             fileNameEditText.setText(fileNamePath);
2837
2838                             // Move the cursor to the end of the file name edit text.
2839                             fileNameEditText.setSelection(fileNamePath.length());
2840                         }
2841                     }
2842                 }
2843                 break;
2844         }
2845     }
2846
2847     private void loadUrlFromTextBox() {
2848         // Get a handle for the URL edit text.
2849         EditText urlEditText = findViewById(R.id.url_edittext);
2850
2851         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
2852         String unformattedUrlString = urlEditText.getText().toString().trim();
2853
2854         // Initialize the formatted URL string.
2855         String url = "";
2856
2857         // Check to see if `unformattedUrlString` is a valid URL.  Otherwise, convert it into a search.
2858         if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
2859             // Load the entire content URL.
2860             url = unformattedUrlString;
2861         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2862                 unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
2863             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
2864             if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
2865                 unformattedUrlString = "https://" + unformattedUrlString;
2866             }
2867
2868             // Initialize `unformattedUrl`.
2869             URL unformattedUrl = null;
2870
2871             // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
2872             try {
2873                 unformattedUrl = new URL(unformattedUrlString);
2874             } catch (MalformedURLException e) {
2875                 e.printStackTrace();
2876             }
2877
2878             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
2879             String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
2880             String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
2881             String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
2882             String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
2883             String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
2884
2885             // Build the URI.
2886             Uri.Builder uri = new Uri.Builder();
2887             uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
2888
2889             // Decode the URI as a UTF-8 string in.
2890             try {
2891                 url = URLDecoder.decode(uri.build().toString(), "UTF-8");
2892             } catch (UnsupportedEncodingException exception) {
2893                 // Do nothing.  The formatted URL string will remain blank.
2894             }
2895         } else if (!unformattedUrlString.isEmpty()){  // This is not a URL, but rather a search string.
2896             // Create an encoded URL String.
2897             String encodedUrlString;
2898
2899             // Sanitize the search input.
2900             try {
2901                 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
2902             } catch (UnsupportedEncodingException exception) {
2903                 encodedUrlString = "";
2904             }
2905
2906             // Add the base search URL.
2907             url = searchURL + encodedUrlString;
2908         }
2909
2910         // Clear the focus from the URL edit text.  Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
2911         urlEditText.clearFocus();
2912
2913         // Make it so.
2914         loadUrl(currentWebView, url);
2915     }
2916
2917     private void loadUrl(NestedScrollWebView nestedScrollWebView, String url) {
2918         // Sanitize the URL.
2919         url = sanitizeUrl(url);
2920
2921         // Apply the domain settings.
2922         applyDomainSettings(nestedScrollWebView, url, true, false);
2923
2924         // Load the URL.
2925         nestedScrollWebView.loadUrl(url, customHeaders);
2926     }
2927
2928     public void findPreviousOnPage(View view) {
2929         // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
2930         currentWebView.findNext(false);
2931     }
2932
2933     public void findNextOnPage(View view) {
2934         // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
2935         currentWebView.findNext(true);
2936     }
2937
2938     public void closeFindOnPage(View view) {
2939         // Get a handle for the views.
2940         Toolbar toolbar = findViewById(R.id.toolbar);
2941         LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2942         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2943
2944         // Delete the contents of `find_on_page_edittext`.
2945         findOnPageEditText.setText(null);
2946
2947         // Clear the highlighted phrases if the WebView is not null.
2948         if (currentWebView != null) {
2949             currentWebView.clearMatches();
2950         }
2951
2952         // Hide the find on page linear layout.
2953         findOnPageLinearLayout.setVisibility(View.GONE);
2954
2955         // Show the toolbar.
2956         toolbar.setVisibility(View.VISIBLE);
2957
2958         // Get a handle for the input method manager.
2959         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2960
2961         // Remove the lint warning below that the input method manager might be null.
2962         assert inputMethodManager != null;
2963
2964         // Hide the keyboard.
2965         inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
2966     }
2967
2968     @Override
2969     public void onApplyNewFontSize(DialogFragment dialogFragment) {
2970         // Get the dialog.
2971         Dialog dialog = dialogFragment.getDialog();
2972
2973         // Remove the incorrect lint warning below tha the dialog might be null.
2974         assert dialog != null;
2975
2976         // Get a handle for the font size edit text.
2977         EditText fontSizeEditText = dialog.findViewById(R.id.font_size_edittext);
2978
2979         // Initialize the new font size variable with the current font size.
2980         int newFontSize = currentWebView.getSettings().getTextZoom();
2981
2982         // Get the font size from the edit text.
2983         try {
2984             newFontSize = Integer.parseInt(fontSizeEditText.getText().toString());
2985         } catch (Exception exception) {
2986             // If the edit text does not contain a valid font size do nothing.
2987         }
2988
2989         // Apply the new font size.
2990         currentWebView.getSettings().setTextZoom(newFontSize);
2991     }
2992
2993     @Override
2994     public void onOpen(DialogFragment dialogFragment) {
2995         // Get the dialog.
2996         Dialog dialog = dialogFragment.getDialog();
2997
2998         // Remove the incorrect lint warning below that the dialog might be null.
2999         assert dialog != null;
3000
3001         // Get a handle for the file name edit text.
3002         EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
3003
3004         // Get the file path string.