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