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