]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
8e01f28731ddc4711673582095fce80f0b5ee499
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
1 /*
2  * Copyright © 2015-2022 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 Android <https://www.stoutner.com/privacy-browser-android>.
7  *
8  * Privacy Browser Android 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 Android 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 Android.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 package com.stoutner.privacybrowser.activities;
23
24 import android.animation.ObjectAnimator;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.Dialog;
28 import android.app.DownloadManager;
29 import android.app.SearchManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ClipData;
33 import android.content.ClipboardManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences;
38 import android.content.pm.PackageManager;
39 import android.content.res.Configuration;
40 import android.database.Cursor;
41 import android.graphics.Bitmap;
42 import android.graphics.BitmapFactory;
43 import android.graphics.Typeface;
44 import android.graphics.drawable.BitmapDrawable;
45 import android.graphics.drawable.Drawable;
46 import android.net.Uri;
47 import android.net.http.SslCertificate;
48 import android.net.http.SslError;
49 import android.os.AsyncTask;
50 import android.os.Build;
51 import android.os.Bundle;
52 import android.os.Environment;
53 import android.os.Handler;
54 import android.os.Message;
55 import android.preference.PreferenceManager;
56 import android.print.PrintDocumentAdapter;
57 import android.print.PrintManager;
58 import android.provider.DocumentsContract;
59 import android.provider.OpenableColumns;
60 import android.text.Editable;
61 import android.text.Spanned;
62 import android.text.TextWatcher;
63 import android.text.style.ForegroundColorSpan;
64 import android.util.Patterns;
65 import android.util.TypedValue;
66 import android.view.ContextMenu;
67 import android.view.GestureDetector;
68 import android.view.KeyEvent;
69 import android.view.Menu;
70 import android.view.MenuItem;
71 import android.view.MotionEvent;
72 import android.view.View;
73 import android.view.ViewGroup;
74 import android.view.WindowManager;
75 import android.view.inputmethod.InputMethodManager;
76 import android.webkit.CookieManager;
77 import android.webkit.HttpAuthHandler;
78 import android.webkit.SslErrorHandler;
79 import android.webkit.ValueCallback;
80 import android.webkit.WebBackForwardList;
81 import android.webkit.WebChromeClient;
82 import android.webkit.WebResourceRequest;
83 import android.webkit.WebResourceResponse;
84 import android.webkit.WebSettings;
85 import android.webkit.WebStorage;
86 import android.webkit.WebView;
87 import android.webkit.WebViewClient;
88 import android.webkit.WebViewDatabase;
89 import android.widget.ArrayAdapter;
90 import android.widget.CheckBox;
91 import android.widget.CursorAdapter;
92 import android.widget.EditText;
93 import android.widget.FrameLayout;
94 import android.widget.ImageView;
95 import android.widget.LinearLayout;
96 import android.widget.ListView;
97 import android.widget.ProgressBar;
98 import android.widget.RadioButton;
99 import android.widget.RelativeLayout;
100 import android.widget.TextView;
101
102 import androidx.activity.result.ActivityResultCallback;
103 import androidx.activity.result.ActivityResultLauncher;
104 import androidx.activity.result.contract.ActivityResultContracts;
105 import androidx.annotation.NonNull;
106 import androidx.appcompat.app.ActionBar;
107 import androidx.appcompat.app.ActionBarDrawerToggle;
108 import androidx.appcompat.app.AppCompatActivity;
109 import androidx.appcompat.app.AppCompatDelegate;
110 import androidx.appcompat.widget.Toolbar;
111 import androidx.coordinatorlayout.widget.CoordinatorLayout;
112 import androidx.core.content.res.ResourcesCompat;
113 import androidx.core.view.GravityCompat;
114 import androidx.drawerlayout.widget.DrawerLayout;
115 import androidx.fragment.app.DialogFragment;
116 import androidx.fragment.app.Fragment;
117 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
118 import androidx.viewpager.widget.ViewPager;
119 import androidx.webkit.WebSettingsCompat;
120 import androidx.webkit.WebViewFeature;
121
122 import com.google.android.material.appbar.AppBarLayout;
123 import com.google.android.material.floatingactionbutton.FloatingActionButton;
124 import com.google.android.material.navigation.NavigationView;
125 import com.google.android.material.snackbar.Snackbar;
126 import com.google.android.material.tabs.TabLayout;
127
128 import com.stoutner.privacybrowser.R;
129 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
130 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
131 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
132 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
133 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
134 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
135 import com.stoutner.privacybrowser.dataclasses.PendingDialog;
136 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
137 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
138 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
139 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
140 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
141 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
142 import com.stoutner.privacybrowser.dialogs.OpenDialog;
143 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
144 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
145 import com.stoutner.privacybrowser.dialogs.SaveDialog;
146 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
147 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
148 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
149 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
150 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
151 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
152 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
153 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
154 import com.stoutner.privacybrowser.helpers.ProxyHelper;
155 import com.stoutner.privacybrowser.helpers.SanitizeUrlHelper;
156 import com.stoutner.privacybrowser.views.NestedScrollWebView;
157
158 import java.io.ByteArrayInputStream;
159 import java.io.ByteArrayOutputStream;
160 import java.io.File;
161 import java.io.FileInputStream;
162 import java.io.FileOutputStream;
163 import java.io.IOException;
164 import java.io.InputStream;
165 import java.io.OutputStream;
166 import java.io.UnsupportedEncodingException;
167
168 import java.net.MalformedURLException;
169 import java.net.URL;
170 import java.net.URLDecoder;
171 import java.net.URLEncoder;
172
173 import java.text.NumberFormat;
174
175 import java.util.ArrayList;
176 import java.util.Date;
177 import java.util.HashSet;
178 import java.util.List;
179 import java.util.Objects;
180 import java.util.Set;
181 import java.util.concurrent.ExecutorService;
182 import java.util.concurrent.Executors;
183
184 import kotlin.Pair;
185
186 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
187         EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
188         PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveListener, UrlHistoryDialog.NavigateHistoryListener,
189         WebViewTabFragment.NewTabListener {
190
191     // Define the public static variables.
192     public static ExecutorService executorService = Executors.newFixedThreadPool(4);
193     public static String orbotStatus = "unknown";
194     public static ArrayList<PendingDialog> pendingDialogsArrayList =  new ArrayList<>();
195     public static String proxyMode = ProxyHelper.NONE;
196
197     // Declare the public static variables.
198     public static String currentBookmarksFolder = "";
199     public static boolean restartFromBookmarksActivity;
200     public static WebViewPagerAdapter webViewPagerAdapter;
201
202     // Declare the public static views.
203     public static AppBarLayout appBarLayout;
204
205     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
206     public final static int UNRECOGNIZED_USER_AGENT = -1;
207     public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
208     public final static int SETTINGS_CUSTOM_USER_AGENT = 11;
209     public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
210     public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
211     public final static int DOMAINS_CUSTOM_USER_AGENT = 12;
212
213     // Define the start activity for result request codes.  The public static entry is accessed from `OpenDialog()`.
214     private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
215     public final static int BROWSE_OPEN_REQUEST_CODE = 1;
216
217     // Define the saved instance state constants.
218     private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
219     private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
220     private final String SAVED_TAB_POSITION = "saved_tab_position";
221     private final String PROXY_MODE = "proxy_mode";
222
223     // Define the saved instance state variables.
224     private ArrayList<Bundle> savedStateArrayList;
225     private ArrayList<Bundle> savedNestedScrollWebViewStateArrayList;
226     private int savedTabPosition;
227     private String savedProxyMode;
228
229     // Define the class variables.
230     @SuppressWarnings("rawtypes")
231     AsyncTask populateBlocklists;
232
233     // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
234     // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
235     private NestedScrollWebView currentWebView;
236
237     // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
238     private String searchURL;
239
240     // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
241     private ArrayList<List<String[]>> easyList;
242     private ArrayList<List<String[]>> easyPrivacy;
243     private ArrayList<List<String[]>> fanboysAnnoyanceList;
244     private ArrayList<List<String[]>> fanboysSocialList;
245     private ArrayList<List<String[]>> ultraList;
246     private ArrayList<List<String[]>> ultraPrivacy;
247
248     // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
249     private ActionBarDrawerToggle actionBarDrawerToggle;
250
251     // The color spans are used in `onCreate()` and `highlightUrlText()`.
252     private ForegroundColorSpan redColorSpan;
253     private ForegroundColorSpan initialGrayColorSpan;
254     private ForegroundColorSpan finalGrayColorSpan;
255
256     // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
257     private Cursor bookmarksCursor;
258
259     // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
260     private CursorAdapter bookmarksCursorAdapter;
261
262     // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
263     private String oldFolderNameString;
264
265     // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
266     private ValueCallback<Uri[]> fileChooserCallback;
267
268     // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
269     private int appBarHeight;
270     private int defaultProgressViewStartOffset;
271     private int defaultProgressViewEndOffset;
272
273     // Declare the helpers.
274     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
275     private DomainsDatabaseHelper domainsDatabaseHelper;
276     private ProxyHelper proxyHelper;
277     private SanitizeUrlHelper sanitizeUrlHelper;
278
279     // Declare the class variables
280     private boolean bottomAppBar;
281     private boolean displayingFullScreenVideo;
282     private boolean downloadWithExternalApp;
283     private boolean fullScreenBrowsingModeEnabled;
284     private boolean hideAppBar;
285     private boolean incognitoModeEnabled;
286     private boolean inFullScreenBrowsingMode;
287     private boolean loadingNewIntent;
288     private BroadcastReceiver orbotStatusBroadcastReceiver;
289     private boolean reapplyAppSettingsOnRestart;
290     private boolean reapplyDomainSettingsOnRestart;
291     private boolean sanitizeAmpRedirects;
292     private boolean sanitizeTrackingQueries;
293     private boolean scrollAppBar;
294     private boolean waitingForProxy;
295     private String webViewDefaultUserAgent;
296
297     // Define the class variables.
298     private ObjectAnimator objectAnimator = new ObjectAnimator();
299     private String saveUrlString = "";
300
301     // Declare the class views.
302     private FrameLayout rootFrameLayout;
303     private DrawerLayout drawerLayout;
304     private CoordinatorLayout coordinatorLayout;
305     private Toolbar toolbar;
306     private RelativeLayout urlRelativeLayout;
307     private EditText urlEditText;
308     private ActionBar actionBar;
309     private LinearLayout findOnPageLinearLayout;
310     private LinearLayout tabsLinearLayout;
311     private TabLayout tabLayout;
312     private SwipeRefreshLayout swipeRefreshLayout;
313     private ViewPager webViewPager;
314     private FrameLayout fullScreenVideoFrameLayout;
315
316     // Declare the class menus.
317     private Menu optionsMenu;
318
319     // Declare the class menu items.
320     private MenuItem navigationBackMenuItem;
321     private MenuItem navigationForwardMenuItem;
322     private MenuItem navigationHistoryMenuItem;
323     private MenuItem navigationRequestsMenuItem;
324     private MenuItem optionsPrivacyMenuItem;
325     private MenuItem optionsRefreshMenuItem;
326     private MenuItem optionsCookiesMenuItem;
327     private MenuItem optionsDomStorageMenuItem;
328     private MenuItem optionsSaveFormDataMenuItem;
329     private MenuItem optionsClearDataMenuItem;
330     private MenuItem optionsClearCookiesMenuItem;
331     private MenuItem optionsClearDomStorageMenuItem;
332     private MenuItem optionsClearFormDataMenuItem;
333     private MenuItem optionsBlocklistsMenuItem;
334     private MenuItem optionsEasyListMenuItem;
335     private MenuItem optionsEasyPrivacyMenuItem;
336     private MenuItem optionsFanboysAnnoyanceListMenuItem;
337     private MenuItem optionsFanboysSocialBlockingListMenuItem;
338     private MenuItem optionsUltraListMenuItem;
339     private MenuItem optionsUltraPrivacyMenuItem;
340     private MenuItem optionsBlockAllThirdPartyRequestsMenuItem;
341     private MenuItem optionsProxyMenuItem;
342     private MenuItem optionsProxyNoneMenuItem;
343     private MenuItem optionsProxyTorMenuItem;
344     private MenuItem optionsProxyI2pMenuItem;
345     private MenuItem optionsProxyCustomMenuItem;
346     private MenuItem optionsUserAgentMenuItem;
347     private MenuItem optionsUserAgentPrivacyBrowserMenuItem;
348     private MenuItem optionsUserAgentWebViewDefaultMenuItem;
349     private MenuItem optionsUserAgentFirefoxOnAndroidMenuItem;
350     private MenuItem optionsUserAgentChromeOnAndroidMenuItem;
351     private MenuItem optionsUserAgentSafariOnIosMenuItem;
352     private MenuItem optionsUserAgentFirefoxOnLinuxMenuItem;
353     private MenuItem optionsUserAgentChromiumOnLinuxMenuItem;
354     private MenuItem optionsUserAgentFirefoxOnWindowsMenuItem;
355     private MenuItem optionsUserAgentChromeOnWindowsMenuItem;
356     private MenuItem optionsUserAgentEdgeOnWindowsMenuItem;
357     private MenuItem optionsUserAgentInternetExplorerOnWindowsMenuItem;
358     private MenuItem optionsUserAgentSafariOnMacosMenuItem;
359     private MenuItem optionsUserAgentCustomMenuItem;
360     private MenuItem optionsSwipeToRefreshMenuItem;
361     private MenuItem optionsWideViewportMenuItem;
362     private MenuItem optionsDisplayImagesMenuItem;
363     private MenuItem optionsDarkWebViewMenuItem;
364     private MenuItem optionsFontSizeMenuItem;
365     private MenuItem optionsAddOrEditDomainMenuItem;
366
367     // This variable won't be needed once the class is migrated to Kotlin, as can be seen in LogcatActivity or AboutVersionFragment.
368     private Activity resultLauncherActivityHandle;
369
370     // Define the save URL activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
371     private final ActivityResultLauncher<String> saveUrlActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("text/*"),
372             new ActivityResultCallback<Uri>() {
373                 @Override
374                 public void onActivityResult(Uri fileUri) {
375                     // Only save the URL if the file URI is not null, which happens if the user exited the file picker by pressing back.
376                     if (fileUri != null) {
377                         new SaveUrl(getApplicationContext(), resultLauncherActivityHandle, fileUri, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptCookies()).execute(saveUrlString);
378                     }
379
380                     // Reset the save URL string.
381                     saveUrlString = "";
382                 }
383             });
384
385     // Define the save webpage archive activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
386     private final ActivityResultLauncher<String> saveWebpageArchiveActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("multipart/related"),
387             new ActivityResultCallback<Uri>() {
388                 @Override
389                 public void onActivityResult(Uri fileUri) {
390                     // Only save the webpage archive if the file URI is not null, which happens if the user exited the file picker by pressing back.
391                     if (fileUri != null) {
392                         try {
393                             // Create a temporary MHT file.
394                             File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
395
396                             // Save the temporary MHT file.
397                             currentWebView.saveWebArchive(temporaryMhtFile.toString(), false, callbackValue -> {
398                                 if (callbackValue != null) {  // The temporary MHT file was saved successfully.
399                                     try {
400                                         // Create a temporary MHT file input stream.
401                                         FileInputStream temporaryMhtFileInputStream = new FileInputStream(temporaryMhtFile);
402
403                                         // Get an output stream for the save webpage file path.
404                                         OutputStream mhtOutputStream = getContentResolver().openOutputStream(fileUri);
405
406                                         // Create a transfer byte array.
407                                         byte[] transferByteArray = new byte[1024];
408
409                                         // Create an integer to track the number of bytes read.
410                                         int bytesRead;
411
412                                         // Copy the temporary MHT file input stream to the MHT output stream.
413                                         while ((bytesRead = temporaryMhtFileInputStream.read(transferByteArray)) > 0) {
414                                             mhtOutputStream.write(transferByteArray, 0, bytesRead);
415                                         }
416
417                                         // Close the streams.
418                                         mhtOutputStream.close();
419                                         temporaryMhtFileInputStream.close();
420
421                                         // Initialize the file name string from the file URI last path segment.
422                                         String fileNameString = fileUri.getLastPathSegment();
423
424                                         // Query the exact file name if the API >= 26.
425                                         if (Build.VERSION.SDK_INT >= 26) {
426                                             // Get a cursor from the content resolver.
427                                             Cursor contentResolverCursor = resultLauncherActivityHandle.getContentResolver().query(fileUri, null, null, null);
428
429                                             // Move to the fist row.
430                                             contentResolverCursor.moveToFirst();
431
432                                             // Get the file name from the cursor.
433                                             fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
434
435                                             // Close the cursor.
436                                             contentResolverCursor.close();
437                                         }
438
439                                         // Display a snackbar.
440                                         Snackbar.make(currentWebView, getString(R.string.file_saved) + "  " + fileNameString, Snackbar.LENGTH_SHORT).show();
441                                     } catch (Exception exception) {
442                                         // Display a snackbar with the exception.
443                                         Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
444                                     } finally {
445                                         // Delete the temporary MHT file.
446                                         //noinspection ResultOfMethodCallIgnored
447                                         temporaryMhtFile.delete();
448                                     }
449                                 } else {  // There was an unspecified error while saving the temporary MHT file.
450                                     // Display an error snackbar.
451                                     Snackbar.make(currentWebView, getString(R.string.error_saving_file), Snackbar.LENGTH_INDEFINITE).show();
452                                 }
453                             });
454                         } catch (IOException ioException) {
455                             // Display a snackbar with the IO exception.
456                             Snackbar.make(currentWebView, getString(R.string.error_saving_file) + "  " + ioException, Snackbar.LENGTH_INDEFINITE).show();
457                         }
458                     }
459                 }
460             });
461
462     // Define the save webpage image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
463     private final ActivityResultLauncher<String> saveWebpageImageActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.CreateDocument("image/png"),
464             new ActivityResultCallback<Uri>() {
465                 @Override
466                 public void onActivityResult(Uri fileUri) {
467                     // Only save the webpage image if the file URI is not null, which happens if the user exited the file picker by pressing back.
468                     if (fileUri != null) {
469                         // Save the webpage image.
470                         new SaveWebpageImage(resultLauncherActivityHandle, fileUri, currentWebView).execute();
471                     }
472                 }
473             });
474
475     // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with WebView.
476     @SuppressLint("ClickableViewAccessibility")
477     @Override
478     protected void onCreate(Bundle savedInstanceState) {
479         // Run the default commands.
480         super.onCreate(savedInstanceState);
481
482         // Populate the result launcher activity.  This will no longer be needed once the activity has transitioned to Kotlin.
483         resultLauncherActivityHandle = this;
484
485         // Check to see if the activity has been restarted.
486         if (savedInstanceState != null) {
487             // Store the saved instance state variables.
488             savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
489             savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
490             savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
491             savedProxyMode = savedInstanceState.getString(PROXY_MODE);
492         }
493
494         // Initialize the default preference values the first time the program is run.  `false` keeps this command from resetting any current preferences back to default.
495         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
496
497         // Get a handle for the shared preferences.
498         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
499
500         // Get the preferences.
501         String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
502         boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
503         bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
504
505         // Get the theme entry values string array.
506         String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
507
508         // 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.
509         if (appTheme.equals(appThemeEntryValuesStringArray[1])) {  // The light theme is selected.
510             // Apply the light theme.
511             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
512         } else if (appTheme.equals(appThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
513             // Apply the dark theme.
514             AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
515         } else {  // The system default theme is selected.
516             if (Build.VERSION.SDK_INT >= 28) {  // The system default theme is supported.
517                 // Follow the system default theme.
518                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
519             } else {  // The system default theme is not supported.
520                 // Follow the battery saver mode.
521                 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
522             }
523         }
524
525         // Disable screenshots if not allowed.
526         if (!allowScreenshots) {
527             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
528         }
529
530         // 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.
531         WebView.enableSlowWholeDocumentDraw();
532
533         // Set the content view according to the position of the app bar.
534         if (bottomAppBar) setContentView(R.layout.main_framelayout_bottom_appbar);
535         else setContentView(R.layout.main_framelayout_top_appbar);
536
537         // Get handles for the views.
538         rootFrameLayout = findViewById(R.id.root_framelayout);
539         drawerLayout = findViewById(R.id.drawerlayout);
540         coordinatorLayout = findViewById(R.id.coordinatorlayout);
541         appBarLayout = findViewById(R.id.appbar_layout);
542         toolbar = findViewById(R.id.toolbar);
543         findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
544         tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
545         tabLayout = findViewById(R.id.tablayout);
546         swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
547         webViewPager = findViewById(R.id.webviewpager);
548         NavigationView navigationView = findViewById(R.id.navigationview);
549         fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
550
551         // Get a handle for the navigation menu.
552         Menu navigationMenu = navigationView.getMenu();
553
554         // Get handles for the navigation menu items.
555         navigationBackMenuItem = navigationMenu.findItem(R.id.back);
556         navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
557         navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
558         navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
559
560         // Listen for touches on the navigation menu.
561         navigationView.setNavigationItemSelectedListener(this);
562
563         // Get a handle for the app compat delegate.
564         AppCompatDelegate appCompatDelegate = getDelegate();
565
566         // Set the support action bar.
567         appCompatDelegate.setSupportActionBar(toolbar);
568
569         // Get a handle for the action bar.
570         actionBar = appCompatDelegate.getSupportActionBar();
571
572         // Remove the incorrect lint warning below that the action bar might be null.
573         assert actionBar != null;
574
575         // Add the custom layout, which shows the URL text bar.
576         actionBar.setCustomView(R.layout.url_app_bar);
577         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
578
579         // Get handles for the views in the URL app bar.
580         urlRelativeLayout = findViewById(R.id.url_relativelayout);
581         urlEditText = findViewById(R.id.url_edittext);
582
583         // Create the hamburger icon at the start of the AppBar.
584         actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
585
586         // Initially disable the sliding drawers.  They will be enabled once the blocklists are loaded.
587         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
588
589         // Initialize the web view pager adapter.
590         webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
591
592         // Set the pager adapter on the web view pager.
593         webViewPager.setAdapter(webViewPagerAdapter);
594
595         // Store up to 100 tabs in memory.
596         webViewPager.setOffscreenPageLimit(100);
597
598         // Instantiate the helpers.
599         bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this);
600         domainsDatabaseHelper = new DomainsDatabaseHelper(this);
601         proxyHelper = new ProxyHelper();
602         sanitizeUrlHelper = new SanitizeUrlHelper();
603
604         // Initialize the app.
605         initializeApp();
606
607         // Apply the app settings from the shared preferences.
608         applyAppSettings();
609
610         // Populate the blocklists.
611         populateBlocklists = new PopulateBlocklists(this, this).execute();
612     }
613
614     @Override
615     protected void onNewIntent(Intent intent) {
616         // Run the default commands.
617         super.onNewIntent(intent);
618
619         // Check to see if the app is being restarted from a saved state.
620         if (savedStateArrayList == null || savedStateArrayList.size() == 0) {  // The activity is not being restarted from a saved state.
621             // Get the information from the intent.
622             String intentAction = intent.getAction();
623             Uri intentUriData = intent.getData();
624             String intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT);
625
626             // Determine if this is a web search.
627             boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
628
629             // 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.
630             if (intentUriData != null || intentStringExtra != null || isWebSearch) {
631                 // Exit the full screen video if it is displayed.
632                 if (displayingFullScreenVideo) {
633                     // Exit full screen video mode.
634                     exitFullScreenVideo();
635
636                     // Reload the current WebView.  Otherwise, it can display entirely black.
637                     currentWebView.reload();
638                 }
639
640                 // Get the shared preferences.
641                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
642
643                 // Create a URL string.
644                 String url;
645
646                 // If the intent action is a web search, perform the search.
647                 if (isWebSearch) {  // The intent is a web search.
648                     // Create an encoded URL string.
649                     String encodedUrlString;
650
651                     // Sanitize the search input and convert it to a search.
652                     try {
653                         encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
654                     } catch (UnsupportedEncodingException exception) {
655                         encodedUrlString = "";
656                     }
657
658                     // Add the base search URL.
659                     url = searchURL + encodedUrlString;
660                 } else if (intentUriData != null) {  // The intent contains a URL formatted as a URI.
661                     // Set the intent data as the URL.
662                     url = intentUriData.toString();
663                 } else {  // The intent contains a string, which might be a URL.
664                     // Set the intent string as the URL.
665                     url = intentStringExtra;
666                 }
667
668                 // Add a new tab if specified in the preferences.
669                 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
670                     // Set the loading new intent flag.
671                     loadingNewIntent = true;
672
673                     // Add a new tab.
674                     addNewTab(url, true);
675                 } else {  // Load the URL in the current tab.
676                     // Make it so.
677                     loadUrl(currentWebView, url);
678                 }
679
680                 // Close the navigation drawer if it is open.
681                 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
682                     drawerLayout.closeDrawer(GravityCompat.START);
683                 }
684
685                 // Close the bookmarks drawer if it is open.
686                 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
687                     drawerLayout.closeDrawer(GravityCompat.END);
688                 }
689             }
690         } else {  // The app has been restarted.
691             // Replace the intent that started the app with this one.  This will load the tab after the others have been restored.
692             setIntent(intent);
693         }
694     }
695
696     @Override
697     public void onRestart() {
698         // Run the default commands.
699         super.onRestart();
700
701         // Apply the app settings if returning from the Settings activity.
702         if (reapplyAppSettingsOnRestart) {
703             // Reset the reapply app settings on restart tracker.
704             reapplyAppSettingsOnRestart = false;
705
706             // Apply the app settings.
707             applyAppSettings();
708         }
709
710         // Apply the domain settings if returning from the settings or domains activity.
711         if (reapplyDomainSettingsOnRestart) {
712             // Reset the reapply domain settings on restart tracker.
713             reapplyDomainSettingsOnRestart = false;
714
715             // Reapply the domain settings for each tab.
716             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
717                 // Get the WebView tab fragment.
718                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
719
720                 // Get the fragment view.
721                 View fragmentView = webViewTabFragment.getView();
722
723                 // Only reload the WebViews if they exist.
724                 if (fragmentView != null) {
725                     // Get the nested scroll WebView from the tab fragment.
726                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
727
728                     // Reset the current domain name so the domain settings will be reapplied.
729                     nestedScrollWebView.setCurrentDomainName("");
730
731                     // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
732                     if (nestedScrollWebView.getUrl() != null) {
733                         applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true, false);
734                     }
735                 }
736             }
737         }
738
739         // Update the bookmarks drawer if returning from the Bookmarks activity.
740         if (restartFromBookmarksActivity) {
741             // Close the bookmarks drawer.
742             drawerLayout.closeDrawer(GravityCompat.END);
743
744             // Reload the bookmarks drawer.
745             loadBookmarksFolder();
746
747             // Reset `restartFromBookmarksActivity`.
748             restartFromBookmarksActivity = false;
749         }
750
751         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.  This can be important if the screen was rotated.
752         updatePrivacyIcons(true);
753     }
754
755     // `onStart()` runs after `onCreate()` or `onRestart()`.  This is used instead of `onResume()` so the commands aren't called every time the screen is partially hidden.
756     @Override
757     public void onStart() {
758         // Run the default commands.
759         super.onStart();
760
761         // Resume any WebViews.
762         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
763             // Get the WebView tab fragment.
764             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
765
766             // Get the fragment view.
767             View fragmentView = webViewTabFragment.getView();
768
769             // Only resume the WebViews if they exist (they won't when the app is first created).
770             if (fragmentView != null) {
771                 // Get the nested scroll WebView from the tab fragment.
772                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
773
774                 // Resume the nested scroll WebView.
775                 nestedScrollWebView.onResume();
776             }
777         }
778
779         // Resume the nested scroll WebView JavaScript timers.  This is a global command that resumes JavaScript timers on all WebViews.
780         if (currentWebView != null) {
781             currentWebView.resumeTimers();
782         }
783
784         // Reapply the proxy settings if the system is using a proxy.  This redisplays the appropriate alert dialog.
785         if (!proxyMode.equals(ProxyHelper.NONE)) {
786             applyProxy(false);
787         }
788
789         // Reapply any system UI flags.
790         if (displayingFullScreenVideo || inFullScreenBrowsingMode) {  // The system is displaying a website or a video in full screen mode.
791             /* Hide the system bars.
792              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
793              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
794              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
795              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
796              */
797             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
798                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
799         }
800
801         // Show any pending dialogs.
802         for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
803             // Get the pending dialog from the array list.
804             PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
805
806             // Show the pending dialog.
807             pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
808         }
809
810         // Clear the pending dialogs array list.
811         pendingDialogsArrayList.clear();
812     }
813
814     // `onStop()` runs after `onPause()`.  It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
815     @Override
816     public void onStop() {
817         // Run the default commands.
818         super.onStop();
819
820         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
821             // Get the WebView tab fragment.
822             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
823
824             // Get the fragment view.
825             View fragmentView = webViewTabFragment.getView();
826
827             // Only pause the WebViews if they exist (they won't when the app is first created).
828             if (fragmentView != null) {
829                 // Get the nested scroll WebView from the tab fragment.
830                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
831
832                 // Pause the nested scroll WebView.
833                 nestedScrollWebView.onPause();
834             }
835         }
836
837         // Pause the WebView JavaScript timers.  This is a global command that pauses JavaScript on all WebViews.
838         if (currentWebView != null) {
839             currentWebView.pauseTimers();
840         }
841     }
842
843     @Override
844     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
845         // Run the default commands.
846         super.onSaveInstanceState(savedInstanceState);
847
848         // Create the saved state array lists.
849         ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
850         ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
851
852         // Get the URLs from each tab.
853         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
854             // Get the WebView tab fragment.
855             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
856
857             // Get the fragment view.
858             View fragmentView = webViewTabFragment.getView();
859
860             if (fragmentView != null) {
861                 // Get the nested scroll WebView from the tab fragment.
862                 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
863
864                 // Create saved state bundle.
865                 Bundle savedStateBundle = new Bundle();
866
867                 // Get the current states.
868                 nestedScrollWebView.saveState(savedStateBundle);
869                 Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
870
871                 // Store the saved states in the array lists.
872                 savedStateArrayList.add(savedStateBundle);
873                 savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
874             }
875         }
876
877         // Get the current tab position.
878         int currentTabPosition = tabLayout.getSelectedTabPosition();
879
880         // Store the saved states in the bundle.
881         savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
882         savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
883         savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
884         savedInstanceState.putString(PROXY_MODE, proxyMode);
885     }
886
887     @Override
888     public void onDestroy() {
889         // Unregister the orbot status broadcast receiver if it exists.
890         if (orbotStatusBroadcastReceiver != null) {
891             this.unregisterReceiver(orbotStatusBroadcastReceiver);
892         }
893
894         // Close the bookmarks cursor if it exists.
895         if (bookmarksCursor != null) {
896             bookmarksCursor.close();
897         }
898
899         // Close the bookmarks database if it exists.
900         if (bookmarksDatabaseHelper != null) {
901             bookmarksDatabaseHelper.close();
902         }
903
904         // Stop populating the blocklists if the AsyncTask is running in the background.
905         if (populateBlocklists != null) {
906             populateBlocklists.cancel(true);
907         }
908
909         // Run the default commands.
910         super.onDestroy();
911     }
912
913     @Override
914     public boolean onCreateOptionsMenu(Menu menu) {
915         // Inflate the menu.  This adds items to the action bar if it is present.
916         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
917
918         // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
919         optionsMenu = menu;
920
921         // Get handles for the menu items.
922         optionsPrivacyMenuItem = menu.findItem(R.id.javascript);
923         optionsRefreshMenuItem = menu.findItem(R.id.refresh);
924         MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks);
925         optionsCookiesMenuItem = menu.findItem(R.id.cookies);
926         optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage);
927         optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data);  // Form data can be removed once the minimum API >= 26.
928         optionsClearDataMenuItem = menu.findItem(R.id.clear_data);
929         optionsClearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
930         optionsClearDomStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
931         optionsClearFormDataMenuItem = menu.findItem(R.id.clear_form_data);  // Form data can be removed once the minimum API >= 26.
932         optionsBlocklistsMenuItem = menu.findItem(R.id.blocklists);
933         optionsEasyListMenuItem = menu.findItem(R.id.easylist);
934         optionsEasyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
935         optionsFanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
936         optionsFanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
937         optionsUltraListMenuItem = menu.findItem(R.id.ultralist);
938         optionsUltraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
939         optionsBlockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
940         optionsProxyMenuItem = menu.findItem(R.id.proxy);
941         optionsProxyNoneMenuItem = menu.findItem(R.id.proxy_none);
942         optionsProxyTorMenuItem = menu.findItem(R.id.proxy_tor);
943         optionsProxyI2pMenuItem = menu.findItem(R.id.proxy_i2p);
944         optionsProxyCustomMenuItem = menu.findItem(R.id.proxy_custom);
945         optionsUserAgentMenuItem = menu.findItem(R.id.user_agent);
946         optionsUserAgentPrivacyBrowserMenuItem = menu.findItem(R.id.user_agent_privacy_browser);
947         optionsUserAgentWebViewDefaultMenuItem = menu.findItem(R.id.user_agent_webview_default);
948         optionsUserAgentFirefoxOnAndroidMenuItem = menu.findItem(R.id.user_agent_firefox_on_android);
949         optionsUserAgentChromeOnAndroidMenuItem = menu.findItem(R.id.user_agent_chrome_on_android);
950         optionsUserAgentSafariOnIosMenuItem = menu.findItem(R.id.user_agent_safari_on_ios);
951         optionsUserAgentFirefoxOnLinuxMenuItem = menu.findItem(R.id.user_agent_firefox_on_linux);
952         optionsUserAgentChromiumOnLinuxMenuItem = menu.findItem(R.id.user_agent_chromium_on_linux);
953         optionsUserAgentFirefoxOnWindowsMenuItem = menu.findItem(R.id.user_agent_firefox_on_windows);
954         optionsUserAgentChromeOnWindowsMenuItem = menu.findItem(R.id.user_agent_chrome_on_windows);
955         optionsUserAgentEdgeOnWindowsMenuItem = menu.findItem(R.id.user_agent_edge_on_windows);
956         optionsUserAgentInternetExplorerOnWindowsMenuItem = menu.findItem(R.id.user_agent_internet_explorer_on_windows);
957         optionsUserAgentSafariOnMacosMenuItem = menu.findItem(R.id.user_agent_safari_on_macos);
958         optionsUserAgentCustomMenuItem = menu.findItem(R.id.user_agent_custom);
959         optionsSwipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
960         optionsWideViewportMenuItem = menu.findItem(R.id.wide_viewport);
961         optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images);
962         optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview);
963         optionsFontSizeMenuItem = menu.findItem(R.id.font_size);
964         optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain);
965
966         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
967         updatePrivacyIcons(false);
968
969         // Only display the form data menu items if the API < 26.
970         optionsSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
971         optionsClearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
972
973         // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
974         optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
975
976         // Get the shared preferences.
977         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
978
979         // Get the dark theme and app bar preferences.
980         boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
981
982         // 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.
983         if (displayAdditionalAppBarIcons) {
984             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
985             bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
986             optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
987         } else { //Do not display the additional icons.
988             optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
989             bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
990             optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
991         }
992
993         // Replace Refresh with Stop if a URL is already loading.
994         if (currentWebView != null && currentWebView.getProgress() != 100) {
995             // Set the title.
996             optionsRefreshMenuItem.setTitle(R.string.stop);
997
998             // Set the icon if it is displayed in the app bar.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
999             if (displayAdditionalAppBarIcons) {
1000                 optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
1001             }
1002         }
1003
1004         // Done.
1005         return true;
1006     }
1007
1008     @Override
1009     public boolean onPrepareOptionsMenu(Menu menu) {
1010         // Get a handle for the cookie manager.
1011         CookieManager cookieManager = CookieManager.getInstance();
1012
1013         // Initialize the current user agent string and the font size.
1014         String currentUserAgent = getString(R.string.user_agent_privacy_browser);
1015         int fontSize = 100;
1016
1017         // 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.
1018         if (currentWebView != null) {
1019             // Set the add or edit domain text.
1020             if (currentWebView.getDomainSettingsApplied()) {
1021                 optionsAddOrEditDomainMenuItem.setTitle(R.string.edit_domain_settings);
1022             } else {
1023                 optionsAddOrEditDomainMenuItem.setTitle(R.string.add_domain_settings);
1024             }
1025
1026             // Get the current user agent from the WebView.
1027             currentUserAgent = currentWebView.getSettings().getUserAgentString();
1028
1029             // Get the current font size from the
1030             fontSize = currentWebView.getSettings().getTextZoom();
1031
1032             // Set the status of the menu item checkboxes.
1033             optionsDomStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1034             optionsSaveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData());  // Form data can be removed once the minimum API >= 26.
1035             optionsEasyListMenuItem.setChecked(currentWebView.getEasyListEnabled());
1036             optionsEasyPrivacyMenuItem.setChecked(currentWebView.getEasyPrivacyEnabled());
1037             optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.getFanboysAnnoyanceListEnabled());
1038             optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.getFanboysSocialBlockingListEnabled());
1039             optionsUltraListMenuItem.setChecked(currentWebView.getUltraListEnabled());
1040             optionsUltraPrivacyMenuItem.setChecked(currentWebView.getUltraPrivacyEnabled());
1041             optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.getBlockAllThirdPartyRequests());
1042             optionsSwipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
1043             optionsWideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
1044             optionsDisplayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
1045
1046             // Initialize the display names for the blocklists with the number of blocked requests.
1047             optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
1048             optionsEasyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
1049             optionsEasyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
1050             optionsFanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
1051             optionsFanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
1052             optionsUltraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
1053             optionsUltraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
1054             optionsBlockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
1055
1056             // Enable DOM Storage if JavaScript is enabled.
1057             optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
1058
1059             // Get the current theme status.
1060             int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
1061
1062             // Enable dark WebView if the API is < 33 or if night mode is enabled.
1063             optionsDarkWebViewMenuItem.setEnabled((Build.VERSION.SDK_INT < 33) || (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES));
1064
1065             // Set the checkbox status for dark WebView if the WebView supports it.
1066             if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
1067                 optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings()));
1068             } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
1069                 //noinspection deprecation
1070                 optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
1071             }
1072         }
1073
1074         // Set the cookies menu item checked status.
1075         optionsCookiesMenuItem.setChecked(cookieManager.acceptCookie());
1076
1077         // Enable Clear Cookies if there are any.
1078         optionsClearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
1079
1080         // 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`.
1081         String privateDataDirectoryString = getApplicationInfo().dataDir;
1082
1083         // Get a count of the number of files in the Local Storage directory.
1084         File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
1085         int localStorageDirectoryNumberOfFiles = 0;
1086         if (localStorageDirectory.exists()) {
1087             // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
1088             localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
1089         }
1090
1091         // Get a count of the number of files in the IndexedDB directory.
1092         File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
1093         int indexedDBDirectoryNumberOfFiles = 0;
1094         if (indexedDBDirectory.exists()) {
1095             // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
1096             indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
1097         }
1098
1099         // Enable Clear DOM Storage if there is any.
1100         optionsClearDomStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
1101
1102         // Enable Clear Form Data is there is any.  This can be removed once the minimum API >= 26.
1103         if (Build.VERSION.SDK_INT < 26) {
1104             // Get the WebView database.
1105             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1106
1107             // Enable the clear form data menu item if there is anything to clear.
1108             optionsClearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
1109         }
1110
1111         // Enable Clear Data if any of the submenu items are enabled.
1112         optionsClearDataMenuItem.setEnabled(optionsClearCookiesMenuItem.isEnabled() || optionsClearDomStorageMenuItem.isEnabled() || optionsClearFormDataMenuItem.isEnabled());
1113
1114         // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
1115         optionsFanboysSocialBlockingListMenuItem.setEnabled(!optionsFanboysAnnoyanceListMenuItem.isChecked());
1116
1117         // Set the proxy title and check the applied proxy.
1118         switch (proxyMode) {
1119             case ProxyHelper.NONE:
1120                 // Set the proxy title.
1121                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
1122
1123                 // Check the proxy None radio button.
1124                 optionsProxyNoneMenuItem.setChecked(true);
1125                 break;
1126
1127             case ProxyHelper.TOR:
1128                 // Set the proxy title.
1129                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
1130
1131                 // Check the proxy Tor radio button.
1132                 optionsProxyTorMenuItem.setChecked(true);
1133                 break;
1134
1135             case ProxyHelper.I2P:
1136                 // Set the proxy title.
1137                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
1138
1139                 // Check the proxy I2P radio button.
1140                 optionsProxyI2pMenuItem.setChecked(true);
1141                 break;
1142
1143             case ProxyHelper.CUSTOM:
1144                 // Set the proxy title.
1145                 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
1146
1147                 // Check the proxy Custom radio button.
1148                 optionsProxyCustomMenuItem.setChecked(true);
1149                 break;
1150         }
1151
1152         // Select the current user agent menu item.  A switch statement cannot be used because the user agents are not compile time constants.
1153         if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) {  // Privacy Browser.
1154             // Update the user agent menu item title.
1155             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
1156
1157             // Select the Privacy Browser radio box.
1158             optionsUserAgentPrivacyBrowserMenuItem.setChecked(true);
1159         } else if (currentUserAgent.equals(webViewDefaultUserAgent)) {  // WebView Default.
1160             // Update the user agent menu item title.
1161             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
1162
1163             // Select the WebView Default radio box.
1164             optionsUserAgentWebViewDefaultMenuItem.setChecked(true);
1165         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) {  // Firefox on Android.
1166             // Update the user agent menu item title.
1167             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
1168
1169             // Select the Firefox on Android radio box.
1170             optionsUserAgentFirefoxOnAndroidMenuItem.setChecked(true);
1171         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) {  // Chrome on Android.
1172             // Update the user agent menu item title.
1173             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
1174
1175             // Select the Chrome on Android radio box.
1176             optionsUserAgentChromeOnAndroidMenuItem.setChecked(true);
1177         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) {  // Safari on iOS.
1178             // Update the user agent menu item title.
1179             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
1180
1181             // Select the Safari on iOS radio box.
1182             optionsUserAgentSafariOnIosMenuItem.setChecked(true);
1183         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) {  // Firefox on Linux.
1184             // Update the user agent menu item title.
1185             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
1186
1187             // Select the Firefox on Linux radio box.
1188             optionsUserAgentFirefoxOnLinuxMenuItem.setChecked(true);
1189         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) {  // Chromium on Linux.
1190             // Update the user agent menu item title.
1191             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
1192
1193             // Select the Chromium on Linux radio box.
1194             optionsUserAgentChromiumOnLinuxMenuItem.setChecked(true);
1195         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) {  // Firefox on Windows.
1196             // Update the user agent menu item title.
1197             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
1198
1199             // Select the Firefox on Windows radio box.
1200             optionsUserAgentFirefoxOnWindowsMenuItem.setChecked(true);
1201         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) {  // Chrome on Windows.
1202             // Update the user agent menu item title.
1203             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
1204
1205             // Select the Chrome on Windows radio box.
1206             optionsUserAgentChromeOnWindowsMenuItem.setChecked(true);
1207         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) {  // Edge on Windows.
1208             // Update the user agent menu item title.
1209             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
1210
1211             // Select the Edge on Windows radio box.
1212             optionsUserAgentEdgeOnWindowsMenuItem.setChecked(true);
1213         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) {  // Internet Explorer on Windows.
1214             // Update the user agent menu item title.
1215             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
1216
1217             // Select the Internet on Windows radio box.
1218             optionsUserAgentInternetExplorerOnWindowsMenuItem.setChecked(true);
1219         } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) {  // Safari on macOS.
1220             // Update the user agent menu item title.
1221             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
1222
1223             // Select the Safari on macOS radio box.
1224             optionsUserAgentSafariOnMacosMenuItem.setChecked(true);
1225         } else {  // Custom user agent.
1226             // Update the user agent menu item title.
1227             optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
1228
1229             // Select the Custom radio box.
1230             optionsUserAgentCustomMenuItem.setChecked(true);
1231         }
1232
1233         // Set the font size title.
1234         optionsFontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
1235
1236         // Run all the other default commands.
1237         super.onPrepareOptionsMenu(menu);
1238
1239         // Display the menu.
1240         return true;
1241     }
1242
1243     @Override
1244     public boolean onOptionsItemSelected(MenuItem menuItem) {
1245         // Get a handle for the shared preferences.
1246         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1247
1248         // Get a handle for the cookie manager.
1249         CookieManager cookieManager = CookieManager.getInstance();
1250
1251         // Get the selected menu item ID.
1252         int menuItemId = menuItem.getItemId();
1253
1254         // Run the commands that correlate to the selected menu item.
1255         if (menuItemId == R.id.javascript) {  // JavaScript.
1256             // Toggle the JavaScript status.
1257             currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1258
1259             // Update the privacy icon.
1260             updatePrivacyIcons(true);
1261
1262             // Display a `Snackbar`.
1263             if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScrip is enabled.
1264                 Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1265             } else if (cookieManager.acceptCookie()) {  // JavaScript is disabled, but first-party cookies are enabled.
1266                 Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1267             } else {  // Privacy mode.
1268                 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1269             }
1270
1271             // Reload the current WebView.
1272             currentWebView.reload();
1273
1274             // Consume the event.
1275             return true;
1276         } else if (menuItemId == R.id.refresh) {  // Refresh.
1277             // Run the command that correlates to the current status of the menu item.
1278             if (menuItem.getTitle().equals(getString(R.string.refresh))) {  // The refresh button was pushed.
1279                 // Reload the current WebView.
1280                 currentWebView.reload();
1281             } else {  // The stop button was pushed.
1282                 // Stop the loading of the WebView.
1283                 currentWebView.stopLoading();
1284             }
1285
1286             // Consume the event.
1287             return true;
1288         } else if (menuItemId == R.id.bookmarks) {  // Bookmarks.
1289             // Open the bookmarks drawer.
1290             drawerLayout.openDrawer(GravityCompat.END);
1291
1292             // Consume the event.
1293             return true;
1294         } else if (menuItemId == R.id.cookies) {  // Cookies.
1295             // Switch the first-party cookie status.
1296             cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1297
1298             // Store the cookie status.
1299             currentWebView.setAcceptCookies(cookieManager.acceptCookie());
1300
1301             // Update the menu checkbox.
1302             menuItem.setChecked(cookieManager.acceptCookie());
1303
1304             // Update the privacy icon.
1305             updatePrivacyIcons(true);
1306
1307             // Display a snackbar.
1308             if (cookieManager.acceptCookie()) {  // Cookies are enabled.
1309                 Snackbar.make(webViewPager, R.string.cookies_enabled, Snackbar.LENGTH_SHORT).show();
1310             } else if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is still enabled.
1311                 Snackbar.make(webViewPager, R.string.cookies_disabled, Snackbar.LENGTH_SHORT).show();
1312             } else {  // Privacy mode.
1313                 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1314             }
1315
1316             // Reload the current WebView.
1317             currentWebView.reload();
1318
1319             // Consume the event.
1320             return true;
1321         } else if (menuItemId == R.id.dom_storage) {  // DOM storage.
1322             // Toggle the status of domStorageEnabled.
1323             currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1324
1325             // Update the menu checkbox.
1326             menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1327
1328             // Update the privacy icon.
1329             updatePrivacyIcons(true);
1330
1331             // Display a snackbar.
1332             if (currentWebView.getSettings().getDomStorageEnabled()) {
1333                 Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1334             } else {
1335                 Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1336             }
1337
1338             // Reload the current WebView.
1339             currentWebView.reload();
1340
1341             // Consume the event.
1342             return true;
1343         } else if (menuItemId == R.id.save_form_data) {  // Form data.  This can be removed once the minimum API >= 26.
1344             // Switch the status of saveFormDataEnabled.
1345             currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1346
1347             // Update the menu checkbox.
1348             menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1349
1350             // Display a snackbar.
1351             if (currentWebView.getSettings().getSaveFormData()) {
1352                 Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1353             } else {
1354                 Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1355             }
1356
1357             // Update the privacy icon.
1358             updatePrivacyIcons(true);
1359
1360             // Reload the current WebView.
1361             currentWebView.reload();
1362
1363             // Consume the event.
1364             return true;
1365         } else if (menuItemId == R.id.clear_cookies) {  // Clear cookies.
1366             // Create a snackbar.
1367             Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1368                     .setAction(R.string.undo, v -> {
1369                         // Do nothing because everything will be handled by `onDismissed()` below.
1370                     })
1371                     .addCallback(new Snackbar.Callback() {
1372                         @Override
1373                         public void onDismissed(Snackbar snackbar, int event) {
1374                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1375                                 // Delete the cookies.
1376                                 cookieManager.removeAllCookies(null);
1377                             }
1378                         }
1379                     })
1380                     .show();
1381
1382             // Consume the event.
1383             return true;
1384         } else if (menuItemId == R.id.clear_dom_storage) {  // Clear DOM storage.
1385             // Create a snackbar.
1386             Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1387                     .setAction(R.string.undo, v -> {
1388                         // Do nothing because everything will be handled by `onDismissed()` below.
1389                     })
1390                     .addCallback(new Snackbar.Callback() {
1391                         @Override
1392                         public void onDismissed(Snackbar snackbar, int event) {
1393                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1394                                 // Delete the DOM Storage.
1395                                 WebStorage webStorage = WebStorage.getInstance();
1396                                 webStorage.deleteAllData();
1397
1398                                 // Initialize a handler to manually delete the DOM storage files and directories.
1399                                 Handler deleteDomStorageHandler = new Handler();
1400
1401                                 // Setup a runnable to manually delete the DOM storage files and directories.
1402                                 Runnable deleteDomStorageRunnable = () -> {
1403                                     try {
1404                                         // Get a handle for the runtime.
1405                                         Runtime runtime = Runtime.getRuntime();
1406
1407                                         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1408                                         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1409                                         String privateDataDirectoryString = getApplicationInfo().dataDir;
1410
1411                                         // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1412                                         Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1413
1414                                         // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1415                                         Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1416                                         Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1417                                         Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1418                                         Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1419
1420                                         // Wait for the processes to finish.
1421                                         deleteLocalStorageProcess.waitFor();
1422                                         deleteIndexProcess.waitFor();
1423                                         deleteQuotaManagerProcess.waitFor();
1424                                         deleteQuotaManagerJournalProcess.waitFor();
1425                                         deleteDatabasesProcess.waitFor();
1426                                     } catch (Exception exception) {
1427                                         // Do nothing if an error is thrown.
1428                                     }
1429                                 };
1430
1431                                 // Manually delete the DOM storage files after 200 milliseconds.
1432                                 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1433                             }
1434                         }
1435                     })
1436                     .show();
1437
1438             // Consume the event.
1439             return true;
1440         } else if (menuItemId == R.id.clear_form_data) {  // Clear form data.  This can be remove once the minimum API >= 26.
1441             // Create a snackbar.
1442             Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1443                     .setAction(R.string.undo, v -> {
1444                         // Do nothing because everything will be handled by `onDismissed()` below.
1445                     })
1446                     .addCallback(new Snackbar.Callback() {
1447                         @Override
1448                         public void onDismissed(Snackbar snackbar, int event) {
1449                             if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {  // The snackbar was dismissed without the undo button being pushed.
1450                                 // Get a handle for the webView database.
1451                                 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1452
1453                                 // Delete the form data.
1454                                 webViewDatabase.clearFormData();
1455                             }
1456                         }
1457                     })
1458                     .show();
1459
1460             // Consume the event.
1461             return true;
1462         } else if (menuItemId == R.id.easylist) {  // EasyList.
1463             // Toggle the EasyList status.
1464             currentWebView.setEasyListEnabled(!currentWebView.getEasyListEnabled());
1465
1466             // Update the menu checkbox.
1467             menuItem.setChecked(currentWebView.getEasyListEnabled());
1468
1469             // Reload the current WebView.
1470             currentWebView.reload();
1471
1472             // Consume the event.
1473             return true;
1474         } else if (menuItemId == R.id.easyprivacy) {  // EasyPrivacy.
1475             // Toggle the EasyPrivacy status.
1476             currentWebView.setEasyPrivacyEnabled(!currentWebView.getEasyPrivacyEnabled());
1477
1478             // Update the menu checkbox.
1479             menuItem.setChecked(currentWebView.getEasyPrivacyEnabled());
1480
1481             // Reload the current WebView.
1482             currentWebView.reload();
1483
1484             // Consume the event.
1485             return true;
1486         } else if (menuItemId == R.id.fanboys_annoyance_list) {  // Fanboy's Annoyance List.
1487             // Toggle Fanboy's Annoyance List status.
1488             currentWebView.setFanboysAnnoyanceListEnabled(!currentWebView.getFanboysAnnoyanceListEnabled());
1489
1490             // Update the menu checkbox.
1491             menuItem.setChecked(currentWebView.getFanboysAnnoyanceListEnabled());
1492
1493             // Update the status of Fanboy's Social Blocking List.
1494             optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.getFanboysAnnoyanceListEnabled());
1495
1496             // Reload the current WebView.
1497             currentWebView.reload();
1498
1499             // Consume the event.
1500             return true;
1501         } else if (menuItemId == R.id.fanboys_social_blocking_list) {  // Fanboy's Social Blocking List.
1502             // Toggle Fanboy's Social Blocking List status.
1503             currentWebView.setFanboysSocialBlockingListEnabled(!currentWebView.getFanboysSocialBlockingListEnabled());
1504
1505             // Update the menu checkbox.
1506             menuItem.setChecked(currentWebView.getFanboysSocialBlockingListEnabled());
1507
1508             // Reload the current WebView.
1509             currentWebView.reload();
1510
1511             // Consume the event.
1512             return true;
1513         } else if (menuItemId == R.id.ultralist) {  // UltraList.
1514             // Toggle the UltraList status.
1515             currentWebView.setUltraListEnabled(!currentWebView.getUltraListEnabled());
1516
1517             // Update the menu checkbox.
1518             menuItem.setChecked(currentWebView.getUltraListEnabled());
1519
1520             // Reload the current WebView.
1521             currentWebView.reload();
1522
1523             // Consume the event.
1524             return true;
1525         } else if (menuItemId == R.id.ultraprivacy) {  // UltraPrivacy.
1526             // Toggle the UltraPrivacy status.
1527             currentWebView.setUltraPrivacyEnabled(!currentWebView.getUltraPrivacyEnabled());
1528
1529             // Update the menu checkbox.
1530             menuItem.setChecked(currentWebView.getUltraPrivacyEnabled());
1531
1532             // Reload the current WebView.
1533             currentWebView.reload();
1534
1535             // Consume the event.
1536             return true;
1537         } else if (menuItemId == R.id.block_all_third_party_requests) {  // Block all third-party requests.
1538             //Toggle the third-party requests blocker status.
1539             currentWebView.setBlockAllThirdPartyRequests(!currentWebView.getBlockAllThirdPartyRequests());
1540
1541             // Update the menu checkbox.
1542             menuItem.setChecked(currentWebView.getBlockAllThirdPartyRequests());
1543
1544             // Reload the current WebView.
1545             currentWebView.reload();
1546
1547             // Consume the event.
1548             return true;
1549         } else if (menuItemId == R.id.proxy_none) {  // Proxy - None.
1550             // Update the proxy mode.
1551             proxyMode = ProxyHelper.NONE;
1552
1553             // Apply the proxy mode.
1554             applyProxy(true);
1555
1556             // Consume the event.
1557             return true;
1558         } else if (menuItemId == R.id.proxy_tor) {  // Proxy - Tor.
1559             // Update the proxy mode.
1560             proxyMode = ProxyHelper.TOR;
1561
1562             // Apply the proxy mode.
1563             applyProxy(true);
1564
1565             // Consume the event.
1566             return true;
1567         } else if (menuItemId == R.id.proxy_i2p) {  // Proxy - I2P.
1568             // Update the proxy mode.
1569             proxyMode = ProxyHelper.I2P;
1570
1571             // Apply the proxy mode.
1572             applyProxy(true);
1573
1574             // Consume the event.
1575             return true;
1576         } else if (menuItemId == R.id.proxy_custom) {  // Proxy - Custom.
1577             // Update the proxy mode.
1578             proxyMode = ProxyHelper.CUSTOM;
1579
1580             // Apply the proxy mode.
1581             applyProxy(true);
1582
1583             // Consume the event.
1584             return true;
1585         } else if (menuItemId == R.id.user_agent_privacy_browser) {  // User Agent - Privacy Browser.
1586             // Update the user agent.
1587             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1588
1589             // Reload the current WebView.
1590             currentWebView.reload();
1591
1592             // Consume the event.
1593             return true;
1594         } else if (menuItemId == R.id.user_agent_webview_default) {  // User Agent - WebView Default.
1595             // Update the user agent.
1596             currentWebView.getSettings().setUserAgentString("");
1597
1598             // Reload the current WebView.
1599             currentWebView.reload();
1600
1601             // Consume the event.
1602             return true;
1603         } else if (menuItemId == R.id.user_agent_firefox_on_android) {  // User Agent - Firefox on Android.
1604             // Update the user agent.
1605             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1606
1607             // Reload the current WebView.
1608             currentWebView.reload();
1609
1610             // Consume the event.
1611             return true;
1612         } else if (menuItemId == R.id.user_agent_chrome_on_android) {  // User Agent - Chrome on Android.
1613             // Update the user agent.
1614             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1615
1616             // Reload the current WebView.
1617             currentWebView.reload();
1618
1619             // Consume the event.
1620             return true;
1621         } else if (menuItemId == R.id.user_agent_safari_on_ios) {  // User Agent - Safari on iOS.
1622             // Update the user agent.
1623             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1624
1625             // Reload the current WebView.
1626             currentWebView.reload();
1627
1628             // Consume the event.
1629             return true;
1630         } else if (menuItemId == R.id.user_agent_firefox_on_linux) {  // User Agent - Firefox on Linux.
1631             // Update the user agent.
1632             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1633
1634             // Reload the current WebView.
1635             currentWebView.reload();
1636
1637             // Consume the event.
1638             return true;
1639         } else if (menuItemId == R.id.user_agent_chromium_on_linux) {  // User Agent - Chromium on Linux.
1640             // Update the user agent.
1641             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1642
1643             // Reload the current WebView.
1644             currentWebView.reload();
1645
1646             // Consume the event.
1647             return true;
1648         } else if (menuItemId == R.id.user_agent_firefox_on_windows) {  // User Agent - Firefox on Windows.
1649             // Update the user agent.
1650             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1651
1652             // Reload the current WebView.
1653             currentWebView.reload();
1654
1655             // Consume the event.
1656             return true;
1657         } else if (menuItemId == R.id.user_agent_chrome_on_windows) {  // User Agent - Chrome on Windows.
1658             // Update the user agent.
1659             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1660
1661             // Reload the current WebView.
1662             currentWebView.reload();
1663
1664             // Consume the event.
1665             return true;
1666         } else if (menuItemId == R.id.user_agent_edge_on_windows) {  // User Agent - Edge on Windows.
1667             // Update the user agent.
1668             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1669
1670             // Reload the current WebView.
1671             currentWebView.reload();
1672
1673             // Consume the event.
1674             return true;
1675         } else if (menuItemId == R.id.user_agent_internet_explorer_on_windows) {  // User Agent - Internet Explorer on Windows.
1676             // Update the user agent.
1677             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1678
1679             // Reload the current WebView.
1680             currentWebView.reload();
1681
1682             // Consume the event.
1683             return true;
1684         } else if (menuItemId == R.id.user_agent_safari_on_macos) {  // User Agent - Safari on macOS.
1685             // Update the user agent.
1686             currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1687
1688             // Reload the current WebView.
1689             currentWebView.reload();
1690
1691             // Consume the event.
1692             return true;
1693         } else if (menuItemId == R.id.user_agent_custom) {  // User Agent - Custom.
1694             // Update the user agent.
1695             currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1696
1697             // Reload the current WebView.
1698             currentWebView.reload();
1699
1700             // Consume the event.
1701             return true;
1702         } else if (menuItemId == R.id.font_size) {  // Font size.
1703             // Instantiate the font size dialog.
1704             DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
1705
1706             // Show the font size dialog.
1707             fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
1708
1709             // Consume the event.
1710             return true;
1711         } else if (menuItemId == R.id.swipe_to_refresh) {  // Swipe to refresh.
1712             // Toggle the stored status of swipe to refresh.
1713             currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1714
1715             // Update the swipe refresh layout.
1716             if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
1717                 // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
1718                 swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
1719             } else {  // Swipe to refresh is disabled.
1720                 // Disable the swipe refresh layout.
1721                 swipeRefreshLayout.setEnabled(false);
1722             }
1723
1724             // Consume the event.
1725             return true;
1726         } else if (menuItemId == R.id.wide_viewport) {  // Wide viewport.
1727             // Toggle the viewport.
1728             currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1729
1730             // Consume the event.
1731             return true;
1732         } else if (menuItemId == R.id.display_images) {  // Display images.
1733             // Toggle the displaying of images.
1734             if (currentWebView.getSettings().getLoadsImagesAutomatically()) {  // Images are currently loaded automatically.
1735                 // Disable loading of images.
1736                 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1737
1738                 // Reload the website to remove existing images.
1739                 currentWebView.reload();
1740             } else {  // Images are not currently loaded automatically.
1741                 // Enable loading of images.  Missing images will be loaded without the need for a reload.
1742                 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1743             }
1744
1745             // Consume the event.
1746             return true;
1747         } else if (menuItemId == R.id.dark_webview) {  // Dark WebView.
1748             // Check to see if dark WebView is supported by this WebView.
1749             if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
1750                 // Toggle algorithmic darkening.
1751                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(currentWebView.getSettings(), !WebSettingsCompat.isAlgorithmicDarkeningAllowed(currentWebView.getSettings()));
1752             } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
1753                 // Toggle the dark WebView setting.
1754                 //noinspection deprecation
1755                 if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) {  // Dark WebView is currently enabled.
1756                     // Turn off dark WebView.
1757                     //noinspection deprecation
1758                     WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
1759                 } else {  // Dark WebView is currently disabled.
1760                     // Turn on dark WebView.
1761                     //noinspection deprecation
1762                     WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
1763                 }
1764             }
1765
1766             // Consume the event.
1767             return true;
1768         } else if (menuItemId == R.id.find_on_page) {  // Find on page.
1769             // Get a handle for the views.
1770             Toolbar toolbar = findViewById(R.id.toolbar);
1771             LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1772             EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1773
1774             // Set the minimum height of the find on page linear layout to match the toolbar.
1775             findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1776
1777             // Hide the toolbar.
1778             toolbar.setVisibility(View.GONE);
1779
1780             // Show the find on page linear layout.
1781             findOnPageLinearLayout.setVisibility(View.VISIBLE);
1782
1783             // Display the keyboard.  The app must wait 200 ms before running the command to work around a bug in Android.
1784             // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1785             findOnPageEditText.postDelayed(() -> {
1786                 // Set the focus on the find on page edit text.
1787                 findOnPageEditText.requestFocus();
1788
1789                 // Get a handle for the input method manager.
1790                 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1791
1792                 // Remove the lint warning below that the input method manager might be null.
1793                 assert inputMethodManager != null;
1794
1795                 // Display the keyboard.  `0` sets no input flags.
1796                 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1797             }, 200);
1798
1799             // Consume the event.
1800             return true;
1801         } else if (menuItemId == R.id.print) {  // Print.
1802             // Get a print manager instance.
1803             PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1804
1805             // Remove the lint error below that print manager might be null.
1806             assert printManager != null;
1807
1808             // Create a print document adapter from the current WebView.
1809             PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter(getString(R.string.print));
1810
1811             // Print the document.
1812             printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
1813
1814             // Consume the event.
1815             return true;
1816         } else if (menuItemId == R.id.save_url) {  // Save URL.
1817             // Check the download preference.
1818             if (downloadWithExternalApp) {  // Download with an external app.
1819                 downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
1820             } else {  // Handle the download inside of Privacy Browser.
1821                 // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
1822                 new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
1823                         currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
1824             }
1825
1826             // Consume the event.
1827             return true;
1828         } else if (menuItemId == R.id.save_archive) {
1829             // Open the file picker with a default file name built from the current domain name.
1830             saveWebpageArchiveActivityResultLauncher.launch(currentWebView.getCurrentDomainName() + ".mht");
1831
1832             // Consume the event.
1833             return true;
1834         } else if (menuItemId == R.id.save_image) {  // Save image.
1835             // Open the file picker with a default file name built from the current domain name.
1836             saveWebpageImageActivityResultLauncher.launch(currentWebView.getCurrentDomainName() + ".png");
1837
1838             // Consume the event.
1839             return true;
1840         } else if (menuItemId == R.id.add_to_homescreen) {  // Add to homescreen.
1841             // Instantiate the create home screen shortcut dialog.
1842             DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1843                     currentWebView.getFavoriteOrDefaultIcon());
1844
1845             // Show the create home screen shortcut dialog.
1846             createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1847
1848             // Consume the event.
1849             return true;
1850         } else if (menuItemId == R.id.view_source) {  // View source.
1851             // Create an intent to launch the view source activity.
1852             Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1853
1854             // Add the variables to the intent.
1855             viewSourceIntent.putExtra(ViewSourceActivityKt.CURRENT_URL, currentWebView.getUrl());
1856             viewSourceIntent.putExtra(ViewSourceActivityKt.USER_AGENT, currentWebView.getSettings().getUserAgentString());
1857
1858             // Make it so.
1859             startActivity(viewSourceIntent);
1860
1861             // Consume the event.
1862             return true;
1863         } else if (menuItemId == R.id.share_message) {  // Share a message.
1864             // Prepare the share string.
1865             String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1866
1867             // Create the share intent.
1868             Intent shareMessageIntent = new Intent(Intent.ACTION_SEND);
1869
1870             // Add the share string to the intent.
1871             shareMessageIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1872
1873             // Set the MIME type.
1874             shareMessageIntent.setType("text/plain");
1875
1876             // Set the intent to open in a new task.
1877             shareMessageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1878
1879             // Make it so.
1880             startActivity(Intent.createChooser(shareMessageIntent, getString(R.string.share_message)));
1881
1882             // Consume the event.
1883             return true;
1884         } else if (menuItemId == R.id.share_url) {  // Share URL.
1885             // Create the share intent.
1886             Intent shareUrlIntent = new Intent(Intent.ACTION_SEND);
1887
1888             // Add the URL to the intent.
1889             shareUrlIntent.putExtra(Intent.EXTRA_TEXT, currentWebView.getUrl());
1890
1891             // Add the title to the intent.
1892             shareUrlIntent.putExtra(Intent.EXTRA_SUBJECT, currentWebView.getTitle());
1893
1894             // Set the MIME type.
1895             shareUrlIntent.setType("text/plain");
1896
1897             // Set the intent to open in a new task.
1898             shareUrlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1899
1900             //Make it so.
1901             startActivity(Intent.createChooser(shareUrlIntent, getString(R.string.share_url)));
1902
1903             // Consume the event.
1904             return true;
1905         } else if (menuItemId == R.id.open_with_app) {  // Open with app.
1906             // Open the URL with an outside app.
1907             openWithApp(currentWebView.getUrl());
1908
1909             // Consume the event.
1910             return true;
1911         } else if (menuItemId == R.id.open_with_browser) {  // Open with browser.
1912             // Open the URL with an outside browser.
1913             openWithBrowser(currentWebView.getUrl());
1914
1915             // Consume the event.
1916             return true;
1917         } else if (menuItemId == R.id.add_or_edit_domain) {  // Add or edit domain.
1918             // Check if domain settings currently exist.
1919             if (currentWebView.getDomainSettingsApplied()) {  // Edit the current domain settings.
1920                 // Reapply the domain settings on returning to `MainWebViewActivity`.
1921                 reapplyDomainSettingsOnRestart = true;
1922
1923                 // Create an intent to launch the domains activity.
1924                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1925
1926                 // Add the extra information to the intent.
1927                 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1928                 domainsIntent.putExtra("close_on_back", true);
1929                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1930                 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1931
1932                 // Get the current certificate.
1933                 SslCertificate sslCertificate = currentWebView.getCertificate();
1934
1935                 // Check to see if the SSL certificate is populated.
1936                 if (sslCertificate != null) {
1937                     // Extract the certificate to strings.
1938                     String issuedToCName = sslCertificate.getIssuedTo().getCName();
1939                     String issuedToOName = sslCertificate.getIssuedTo().getOName();
1940                     String issuedToUName = sslCertificate.getIssuedTo().getUName();
1941                     String issuedByCName = sslCertificate.getIssuedBy().getCName();
1942                     String issuedByOName = sslCertificate.getIssuedBy().getOName();
1943                     String issuedByUName = sslCertificate.getIssuedBy().getUName();
1944                     long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1945                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1946
1947                     // Add the certificate to the intent.
1948                     domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1949                     domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1950                     domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1951                     domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1952                     domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1953                     domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1954                     domainsIntent.putExtra("ssl_start_date", startDateLong);
1955                     domainsIntent.putExtra("ssl_end_date", endDateLong);
1956                 }
1957
1958                 // Make it so.
1959                 startActivity(domainsIntent);
1960             } else {  // Add a new domain.
1961                 // Apply the new domain settings on returning to `MainWebViewActivity`.
1962                 reapplyDomainSettingsOnRestart = true;
1963
1964                 // Get the current domain
1965                 Uri currentUri = Uri.parse(currentWebView.getUrl());
1966                 String currentDomain = currentUri.getHost();
1967
1968                 // Create the domain and store the database ID.
1969                 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1970
1971                 // Create an intent to launch the domains activity.
1972                 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1973
1974                 // Add the extra information to the intent.
1975                 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1976                 domainsIntent.putExtra("close_on_back", true);
1977                 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1978                 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1979
1980                 // Get the current certificate.
1981                 SslCertificate sslCertificate = currentWebView.getCertificate();
1982
1983                 // Check to see if the SSL certificate is populated.
1984                 if (sslCertificate != null) {
1985                     // Extract the certificate to strings.
1986                     String issuedToCName = sslCertificate.getIssuedTo().getCName();
1987                     String issuedToOName = sslCertificate.getIssuedTo().getOName();
1988                     String issuedToUName = sslCertificate.getIssuedTo().getUName();
1989                     String issuedByCName = sslCertificate.getIssuedBy().getCName();
1990                     String issuedByOName = sslCertificate.getIssuedBy().getOName();
1991                     String issuedByUName = sslCertificate.getIssuedBy().getUName();
1992                     long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1993                     long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1994
1995                     // Add the certificate to the intent.
1996                     domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1997                     domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1998                     domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1999                     domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2000                     domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2001                     domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2002                     domainsIntent.putExtra("ssl_start_date", startDateLong);
2003                     domainsIntent.putExtra("ssl_end_date", endDateLong);
2004                 }
2005
2006                 // Make it so.
2007                 startActivity(domainsIntent);
2008             }
2009
2010             // Consume the event.
2011             return true;
2012         } else {  // There is no match with the options menu.  Pass the event up to the parent method.
2013             // Don't consume the event.
2014             return super.onOptionsItemSelected(menuItem);
2015         }
2016     }
2017
2018     // removeAllCookies is deprecated, but it is required for API < 21.
2019     @Override
2020     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2021         // Get a handle for the shared preferences.
2022         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2023
2024         // Get the menu item ID.
2025         int menuItemId = menuItem.getItemId();
2026
2027         // Run the commands that correspond to the selected menu item.
2028         if (menuItemId == R.id.clear_and_exit) {  // Clear and exit.
2029             // Clear and exit Privacy Browser.
2030             clearAndExit();
2031         } else if (menuItemId == R.id.home) {  // Home.
2032             // Load the homepage.
2033             loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
2034         } else if (menuItemId == R.id.back) {  // Back.
2035             // Check if the WebView can go back.
2036             if (currentWebView.canGoBack()) {
2037                 // Get the current web back forward list.
2038                 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2039
2040                 // Get the previous entry URL.
2041                 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2042
2043                 // Apply the domain settings.
2044                 applyDomainSettings(currentWebView, previousUrl, false, false, false);
2045
2046                 // Load the previous website in the history.
2047                 currentWebView.goBack();
2048             }
2049         } else if (menuItemId == R.id.forward) {  // Forward.
2050             // Check if the WebView can go forward.
2051             if (currentWebView.canGoForward()) {
2052                 // Get the current web back forward list.
2053                 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2054
2055                 // Get the next entry URL.
2056                 String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
2057
2058                 // Apply the domain settings.
2059                 applyDomainSettings(currentWebView, nextUrl, false, false, false);
2060
2061                 // Load the next website in the history.
2062                 currentWebView.goForward();
2063             }
2064         } else if (menuItemId == R.id.history) {  // History.
2065             // Instantiate the URL history dialog.
2066             DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
2067
2068             // Show the URL history dialog.
2069             urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2070         } else if (menuItemId == R.id.open) {  // Open.
2071             // Instantiate the open file dialog.
2072             DialogFragment openDialogFragment = new OpenDialog();
2073
2074             // Show the open file dialog.
2075             openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
2076         } else if (menuItemId == R.id.requests) {  // Requests.
2077             // Populate the resource requests.
2078             RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
2079
2080             // Create an intent to launch the Requests activity.
2081             Intent requestsIntent = new Intent(this, RequestsActivity.class);
2082
2083             // Add the block third-party requests status to the intent.
2084             requestsIntent.putExtra("block_all_third_party_requests", currentWebView.getBlockAllThirdPartyRequests());
2085
2086             // Make it so.
2087             startActivity(requestsIntent);
2088         } else if (menuItemId == R.id.downloads) {  // Downloads.
2089             // Try the default system download manager.
2090             try {
2091                 // Launch the default system Download Manager.
2092                 Intent defaultDownloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2093
2094                 // Launch as a new task so that the download manager and Privacy Browser show as separate windows in the recent tasks list.
2095                 defaultDownloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2096
2097                 // Make it so.
2098                 startActivity(defaultDownloadManagerIntent);
2099             } catch (Exception defaultDownloadManagerException) {
2100                 // Try a generic file manager.
2101                 try {
2102                     // Create a generic file manager intent.
2103                     Intent genericFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2104
2105                     // Open the download directory.
2106                     genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR);
2107
2108                     // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2109                     genericFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2110
2111                     // Make it so.
2112                     startActivity(genericFileManagerIntent);
2113                 } catch (Exception genericFileManagerException) {
2114                     // Try an alternate file manager.
2115                     try {
2116                         // Create an alternate file manager intent.
2117                         Intent alternateFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2118
2119                         // Open the download directory.
2120                         alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder");
2121
2122                         // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2123                         alternateFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2124
2125                         // Open the alternate file manager.
2126                         startActivity(alternateFileManagerIntent);
2127                     } catch (Exception alternateFileManagerException) {
2128                         // Display a snackbar.
2129                         Snackbar.make(currentWebView, R.string.no_file_manager_detected, Snackbar.LENGTH_INDEFINITE).show();
2130                     }
2131                 }
2132             }
2133         } else if (menuItemId == R.id.domains) {  // Domains.
2134             // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2135             reapplyDomainSettingsOnRestart = true;
2136
2137             // Launch the domains activity.
2138             Intent domainsIntent = new Intent(this, DomainsActivity.class);
2139
2140             // Add the extra information to the intent.
2141             domainsIntent.putExtra("current_url", currentWebView.getUrl());
2142             domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2143
2144             // Get the current certificate.
2145             SslCertificate sslCertificate = currentWebView.getCertificate();
2146
2147             // Check to see if the SSL certificate is populated.
2148             if (sslCertificate != null) {
2149                 // Extract the certificate to strings.
2150                 String issuedToCName = sslCertificate.getIssuedTo().getCName();
2151                 String issuedToOName = sslCertificate.getIssuedTo().getOName();
2152                 String issuedToUName = sslCertificate.getIssuedTo().getUName();
2153                 String issuedByCName = sslCertificate.getIssuedBy().getCName();
2154                 String issuedByOName = sslCertificate.getIssuedBy().getOName();
2155                 String issuedByUName = sslCertificate.getIssuedBy().getUName();
2156                 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2157                 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2158
2159                 // Add the certificate to the intent.
2160                 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2161                 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2162                 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2163                 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2164                 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2165                 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2166                 domainsIntent.putExtra("ssl_start_date", startDateLong);
2167                 domainsIntent.putExtra("ssl_end_date", endDateLong);
2168             }
2169
2170             // Make it so.
2171             startActivity(domainsIntent);
2172         } else if (menuItemId == R.id.settings) {  // Settings.
2173             // Set the flag to reapply app settings on restart when returning from Settings.
2174             reapplyAppSettingsOnRestart = true;
2175
2176             // Set the flag to reapply the domain settings on restart when returning from Settings.
2177             reapplyDomainSettingsOnRestart = true;
2178
2179             // Launch the settings activity.
2180             Intent settingsIntent = new Intent(this, SettingsActivity.class);
2181             startActivity(settingsIntent);
2182         } else if (menuItemId == R.id.import_export) { // Import/Export.
2183             // Create an intent to launch the import/export activity.
2184             Intent importExportIntent = new Intent(this, ImportExportActivity.class);
2185
2186             // Make it so.
2187             startActivity(importExportIntent);
2188         } else if (menuItemId == R.id.logcat) {  // Logcat.
2189             // Create an intent to launch the logcat activity.
2190             Intent logcatIntent = new Intent(this, LogcatActivity.class);
2191
2192             // Make it so.
2193             startActivity(logcatIntent);
2194         } else if (menuItemId == R.id.guide) {  // Guide.
2195             // Create an intent to launch the guide activity.
2196             Intent guideIntent = new Intent(this, GuideActivity.class);
2197
2198             // Make it so.
2199             startActivity(guideIntent);
2200         } else if (menuItemId == R.id.about) {  // About
2201             // Create an intent to launch the about activity.
2202             Intent aboutIntent = new Intent(this, AboutActivity.class);
2203
2204             // Create a string array for the blocklist versions.
2205             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],
2206                     ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
2207
2208             // Add the blocklist versions to the intent.
2209             aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions);
2210
2211             // Make it so.
2212             startActivity(aboutIntent);
2213         }
2214
2215         // Close the navigation drawer.
2216         drawerLayout.closeDrawer(GravityCompat.START);
2217         return true;
2218     }
2219
2220     @Override
2221     public void onPostCreate(Bundle savedInstanceState) {
2222         // Run the default commands.
2223         super.onPostCreate(savedInstanceState);
2224
2225         // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished.  This creates the navigation drawer icon.
2226         actionBarDrawerToggle.syncState();
2227     }
2228
2229     @Override
2230     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2231         // Get the hit test result.
2232         final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2233
2234         // Define the URL strings.
2235         final String imageUrl;
2236         final String linkUrl;
2237
2238         // Get handles for the system managers.
2239         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2240
2241         // Remove the lint errors below that the clipboard manager might be null.
2242         assert clipboardManager != null;
2243
2244         // Process the link according to the type.
2245         switch (hitTestResult.getType()) {
2246             // `SRC_ANCHOR_TYPE` is a link.
2247             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2248                 // Get the target URL.
2249                 linkUrl = hitTestResult.getExtra();
2250
2251                 // Set the target URL as the title of the `ContextMenu`.
2252                 menu.setHeaderTitle(linkUrl);
2253
2254                 // Add an Open in New Tab entry.
2255                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2256                     // Load the link URL in a new tab and move to it.
2257                     addNewTab(linkUrl, true);
2258
2259                     // Consume the event.
2260                     return true;
2261                 });
2262
2263                 // Add an Open in Background entry.
2264                 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2265                     // Load the link URL in a new tab but do not move to it.
2266                     addNewTab(linkUrl, false);
2267
2268                     // Consume the event.
2269                     return true;
2270                 });
2271
2272                 // Add an Open with App entry.
2273                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2274                     openWithApp(linkUrl);
2275
2276                     // Consume the event.
2277                     return true;
2278                 });
2279
2280                 // Add an Open with Browser entry.
2281                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2282                     openWithBrowser(linkUrl);
2283
2284                     // Consume the event.
2285                     return true;
2286                 });
2287
2288                 // Add a Copy URL entry.
2289                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2290                     // Save the link URL in a `ClipData`.
2291                     ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2292
2293                     // Set the `ClipData` as the clipboard's primary clip.
2294                     clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2295
2296                     // Consume the event.
2297                     return true;
2298                 });
2299
2300                 // Add a Save URL entry.
2301                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2302                     // Check the download preference.
2303                     if (downloadWithExternalApp) {  // Download with an external app.
2304                         downloadUrlWithExternalApp(linkUrl);
2305                     } else {  // Handle the download inside of Privacy Browser.
2306                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2307                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
2308                                 currentWebView.getAcceptCookies()).execute(linkUrl);
2309                     }
2310
2311                     // Consume the event.
2312                     return true;
2313                 });
2314
2315                 // Add an empty Cancel entry, which by default closes the context menu.
2316                 menu.add(R.string.cancel);
2317                 break;
2318
2319             // `IMAGE_TYPE` is an image.
2320             case WebView.HitTestResult.IMAGE_TYPE:
2321                 // Get the image URL.
2322                 imageUrl = hitTestResult.getExtra();
2323
2324                 // Remove the incorrect lint warning below that the image URL might be null.
2325                 assert imageUrl != null;
2326
2327                 // Set the context menu title.
2328                 if (imageUrl.startsWith("data:")) {  // The image data is contained in within the URL, making it exceedingly long.
2329                     // Truncate the image URL before making it the title.
2330                     menu.setHeaderTitle(imageUrl.substring(0, 100));
2331                 } else {  // The image URL does not contain the full image data.
2332                     // Set the image URL as the title of the context menu.
2333                     menu.setHeaderTitle(imageUrl);
2334                 }
2335
2336                 // Add an Open in New Tab entry.
2337                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2338                     // Load the image in a new tab.
2339                     addNewTab(imageUrl, true);
2340
2341                     // Consume the event.
2342                     return true;
2343                 });
2344
2345                 // Add an Open with App entry.
2346                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2347                     // Open the image URL with an external app.
2348                     openWithApp(imageUrl);
2349
2350                     // Consume the event.
2351                     return true;
2352                 });
2353
2354                 // Add an Open with Browser entry.
2355                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2356                     // Open the image URL with an external browser.
2357                     openWithBrowser(imageUrl);
2358
2359                     // Consume the event.
2360                     return true;
2361                 });
2362
2363                 // Add a View Image entry.
2364                 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2365                     // Load the image in the current tab.
2366                     loadUrl(currentWebView, imageUrl);
2367
2368                     // Consume the event.
2369                     return true;
2370                 });
2371
2372                 // Add a Save Image entry.
2373                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2374                     // Check the download preference.
2375                     if (downloadWithExternalApp) {  // Download with an external app.
2376                         downloadUrlWithExternalApp(imageUrl);
2377                     } else {  // Handle the download inside of Privacy Browser.
2378                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2379                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
2380                                 currentWebView.getAcceptCookies()).execute(imageUrl);
2381                     }
2382
2383                     // Consume the event.
2384                     return true;
2385                 });
2386
2387                 // Add a Copy URL entry.
2388                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2389                     // Save the image URL in a clip data.
2390                     ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2391
2392                     // Set the clip data as the clipboard's primary clip.
2393                     clipboardManager.setPrimaryClip(imageTypeClipData);
2394
2395                     // Consume the event.
2396                     return true;
2397                 });
2398
2399                 // Add an empty Cancel entry, which by default closes the context menu.
2400                 menu.add(R.string.cancel);
2401                 break;
2402
2403             // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2404             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2405                 // Get the image URL.
2406                 imageUrl = hitTestResult.getExtra();
2407
2408                 // Instantiate a handler.
2409                 Handler handler = new Handler();
2410
2411                 // Get a message from the handler.
2412                 Message message = handler.obtainMessage();
2413
2414                 // Request the image details from the last touched node be returned in the message.
2415                 currentWebView.requestFocusNodeHref(message);
2416
2417                 // Get the link URL from the message data.
2418                 linkUrl = message.getData().getString("url");
2419
2420                 // Set the link URL as the title of the context menu.
2421                 menu.setHeaderTitle(linkUrl);
2422
2423                 // Add an Open in New Tab entry.
2424                 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2425                     // Load the link URL in a new tab and move to it.
2426                     addNewTab(linkUrl, true);
2427
2428                     // Consume the event.
2429                     return true;
2430                 });
2431
2432                 // Add an Open in Background entry.
2433                 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2434                     // Lod the link URL in a new tab but do not move to it.
2435                     addNewTab(linkUrl, false);
2436
2437                     // Consume the event.
2438                     return true;
2439                 });
2440
2441                 // Add an Open Image in New Tab entry.
2442                 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2443                     // Load the image in a new tab and move to it.
2444                     addNewTab(imageUrl, true);
2445
2446                     // Consume the event.
2447                     return true;
2448                 });
2449
2450                 // Add an Open with App entry.
2451                 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2452                     // Open the link URL with an external app.
2453                     openWithApp(linkUrl);
2454
2455                     // Consume the event.
2456                     return true;
2457                 });
2458
2459                 // Add an Open with Browser entry.
2460                 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2461                     // Open the link URL with an external browser.
2462                     openWithBrowser(linkUrl);
2463
2464                     // Consume the event.
2465                     return true;
2466                 });
2467
2468                 // Add a View Image entry.
2469                 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2470                    // View the image in the current tab.
2471                    loadUrl(currentWebView, imageUrl);
2472
2473                    // Consume the event.
2474                    return true;
2475                 });
2476
2477                 // Add a Save Image entry.
2478                 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2479                     // Check the download preference.
2480                     if (downloadWithExternalApp) {  // Download with an external app.
2481                         downloadUrlWithExternalApp(imageUrl);
2482                     } else {  // Handle the download inside of Privacy Browser.
2483                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2484                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
2485                                 currentWebView.getAcceptCookies()).execute(imageUrl);
2486                     }
2487
2488                     // Consume the event.
2489                     return true;
2490                 });
2491
2492                 // Add a Copy URL entry.
2493                 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2494                     // Save the link URL in a clip data.
2495                     ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2496
2497                     // Set the clip data as the clipboard's primary clip.
2498                     clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2499
2500                     // Consume the event.
2501                     return true;
2502                 });
2503
2504                 // Add a Save URL entry.
2505                 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2506                     // Check the download preference.
2507                     if (downloadWithExternalApp) {  // Download with an external app.
2508                         downloadUrlWithExternalApp(linkUrl);
2509                     } else {  // Handle the download inside of Privacy Browser.
2510                         // Prepare the save dialog.  The dialog will be displayed once the file size and the content disposition have been acquired.
2511                         new PrepareSaveDialog(this, this, getSupportFragmentManager(), currentWebView.getSettings().getUserAgentString(),
2512                                 currentWebView.getAcceptCookies()).execute(linkUrl);
2513                     }
2514
2515                     // Consume the event.
2516                     return true;
2517                 });
2518
2519                 // Add an empty Cancel entry, which by default closes the context menu.
2520                 menu.add(R.string.cancel);
2521                 break;
2522
2523             case WebView.HitTestResult.EMAIL_TYPE:
2524                 // Get the target URL.
2525                 linkUrl = hitTestResult.getExtra();
2526
2527                 // Set the target URL as the title of the `ContextMenu`.
2528                 menu.setHeaderTitle(linkUrl);
2529
2530                 // Add a Write Email entry.
2531                 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2532                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2533                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2534
2535                     // Parse the url and set it as the data for the `Intent`.
2536                     emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2537
2538                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2539                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2540
2541                     try {
2542                         // Make it so.
2543                         startActivity(emailIntent);
2544                     } catch (ActivityNotFoundException exception) {
2545                         // Display a snackbar.
2546                         Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
2547                     }
2548
2549                     // Consume the event.
2550                     return true;
2551                 });
2552
2553                 // Add a Copy Email Address entry.
2554                 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2555                     // Save the email address in a `ClipData`.
2556                     ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2557
2558                     // Set the `ClipData` as the clipboard's primary clip.
2559                     clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2560
2561                     // Consume the event.
2562                     return true;
2563                 });
2564
2565                 // Add an empty Cancel entry, which by default closes the context menu.
2566                 menu.add(R.string.cancel);
2567                 break;
2568         }
2569     }
2570
2571     @Override
2572     public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2573         // Get a handle for the bookmarks list view.
2574         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2575
2576         // Get the dialog.
2577         Dialog dialog = dialogFragment.getDialog();
2578
2579         // Remove the incorrect lint warning below that the dialog might be null.
2580         assert dialog != null;
2581
2582         // Get the views from the dialog fragment.
2583         EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2584         EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2585
2586         // Extract the strings from the edit texts.
2587         String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2588         String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2589
2590         // Create a favorite icon byte array output stream.
2591         ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2592
2593         // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2594         favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2595
2596         // Convert the favorite icon byte array stream to a byte array.
2597         byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2598
2599         // Display the new bookmark below the current items in the (0 indexed) list.
2600         int newBookmarkDisplayOrder = bookmarksListView.getCount();
2601
2602         // Create the bookmark.
2603         bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2604
2605         // Update the bookmarks cursor with the current contents of this folder.
2606         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2607
2608         // Update the list view.
2609         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2610
2611         // Scroll to the new bookmark.
2612         bookmarksListView.setSelection(newBookmarkDisplayOrder);
2613     }
2614
2615     @Override
2616     public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
2617         // Get a handle for the bookmarks list view.
2618         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2619
2620         // Get the dialog.
2621         Dialog dialog = dialogFragment.getDialog();
2622
2623         // Remove the incorrect lint warning below that the dialog might be null.
2624         assert dialog != null;
2625
2626         // Get handles for the views in the dialog fragment.
2627         EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2628         RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2629         ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2630
2631         // Get new folder name string.
2632         String folderNameString = folderNameEditText.getText().toString();
2633
2634         // Create a folder icon bitmap.
2635         Bitmap folderIconBitmap;
2636
2637         // Set the folder icon bitmap according to the dialog.
2638         if (defaultIconRadioButton.isChecked()) {  // Use the default folder icon.
2639             // Get the default folder icon drawable.
2640             Drawable folderIconDrawable = defaultIconImageView.getDrawable();
2641
2642             // Convert the folder icon drawable to a bitmap drawable.
2643             BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2644
2645             // Convert the folder icon bitmap drawable to a bitmap.
2646             folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2647         } else {  // Use the WebView favorite icon.
2648             // Copy the favorite icon bitmap to the folder icon bitmap.
2649             folderIconBitmap = favoriteIconBitmap;
2650         }
2651
2652         // Create a folder icon byte array output stream.
2653         ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2654
2655         // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2656         folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2657
2658         // Convert the folder icon byte array stream to a byte array.
2659         byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2660
2661         // Move all the bookmarks down one in the display order.
2662         for (int i = 0; i < bookmarksListView.getCount(); i++) {
2663             int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2664             bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2665         }
2666
2667         // Create the folder, which will be placed at the top of the `ListView`.
2668         bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2669
2670         // Update the bookmarks cursor with the current contents of this folder.
2671         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2672
2673         // Update the `ListView`.
2674         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2675
2676         // Scroll to the new folder.
2677         bookmarksListView.setSelection(0);
2678     }
2679
2680     @Override
2681     public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
2682         // Remove the incorrect lint warning below that the dialog fragment might be null.
2683         assert dialogFragment != null;
2684
2685         // Get the dialog.
2686         Dialog dialog = dialogFragment.getDialog();
2687
2688         // Remove the incorrect lint warning below that the dialog might be null.
2689         assert dialog != null;
2690
2691         // Get handles for the views from the dialog.
2692         RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
2693         RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2694         ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2695         EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2696
2697         // Get the new folder name.
2698         String newFolderNameString = editFolderNameEditText.getText().toString();
2699
2700         // Check if the favorite icon has changed.
2701         if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
2702             // Update the name in the database.
2703             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2704         } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
2705             // Create the new folder icon Bitmap.
2706             Bitmap folderIconBitmap;
2707
2708             // Populate the new folder icon bitmap.
2709             if (defaultFolderIconRadioButton.isChecked()) {
2710                 // Get the default folder icon drawable.
2711                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2712
2713                 // Convert the folder icon drawable to a bitmap drawable.
2714                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2715
2716                 // Convert the folder icon bitmap drawable to a bitmap.
2717                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2718             } else {  // Use the `WebView` favorite icon.
2719                 // Copy the favorite icon bitmap to the folder icon bitmap.
2720                 folderIconBitmap = favoriteIconBitmap;
2721             }
2722
2723             // Create a folder icon byte array output stream.
2724             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2725
2726             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2727             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2728
2729             // Convert the folder icon byte array stream to a byte array.
2730             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2731
2732             // Update the folder icon in the database.
2733             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2734         } else {  // The folder icon and the name have changed.
2735             // Get the new folder icon bitmap.
2736             Bitmap folderIconBitmap;
2737             if (defaultFolderIconRadioButton.isChecked()) {
2738                 // Get the default folder icon drawable.
2739                 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2740
2741                 // Convert the folder icon drawable to a bitmap drawable.
2742                 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2743
2744                 // Convert the folder icon bitmap drawable to a bitmap.
2745                 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2746             } else {  // Use the `WebView` favorite icon.
2747                 // Copy the favorite icon bitmap to the folder icon bitmap.
2748                 folderIconBitmap = favoriteIconBitmap;
2749             }
2750
2751             // Create a folder icon byte array output stream.
2752             ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2753
2754             // Convert the folder icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
2755             folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2756
2757             // Convert the folder icon byte array stream to a byte array.
2758             byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2759
2760             // Update the folder name and icon in the database.
2761             bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2762         }
2763
2764         // Update the bookmarks cursor with the current contents of this folder.
2765         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2766
2767         // Update the `ListView`.
2768         bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2769     }
2770
2771     // Override `onBackPressed()` to handle the navigation drawer and and the WebViews.
2772     @Override
2773     public void onBackPressed() {
2774         // Check the different options for processing `back`.
2775         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
2776             // Close the navigation drawer.
2777             drawerLayout.closeDrawer(GravityCompat.START);
2778         } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
2779             // close the bookmarks drawer.
2780             drawerLayout.closeDrawer(GravityCompat.END);
2781         } else if (displayingFullScreenVideo) {  // A full screen video is shown.
2782             // Exit the full screen video.
2783             exitFullScreenVideo();
2784         } else if (currentWebView.canGoBack()) {  // There is at least one item in the current WebView history.
2785             // Get the current web back forward list.
2786             WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2787
2788             // Get the previous entry URL.
2789             String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2790
2791             // Apply the domain settings.
2792             applyDomainSettings(currentWebView, previousUrl, false, false, false);
2793
2794             // Go back.
2795             currentWebView.goBack();
2796         } else if (tabLayout.getTabCount() > 1) {  // There are at least two tabs.
2797             // Close the current tab.
2798             closeCurrentTab();
2799         } else {  // There isn't anything to do in Privacy Browser.
2800             // Close Privacy Browser.  `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
2801             finishAndRemoveTask();
2802
2803             // Manually kill Privacy Browser.  Otherwise, it is glitchy when restarted.
2804             System.exit(0);
2805         }
2806     }
2807
2808     // Process the results of a file browse.
2809     @Override
2810     public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
2811         // Run the default commands.
2812         super.onActivityResult(requestCode, resultCode, returnedIntent);
2813
2814         // Run the commands that correlate to the specified request code.
2815         switch (requestCode) {
2816             case BROWSE_FILE_UPLOAD_REQUEST_CODE:
2817                 // Pass the file to the WebView.
2818                 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
2819                 break;
2820
2821             case BROWSE_OPEN_REQUEST_CODE:
2822                 // Don't do anything if the user pressed back from the file picker.
2823                 if (resultCode == Activity.RESULT_OK) {
2824                     // Get a handle for the open dialog fragment.
2825                     DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
2826
2827                     // Only update the file name if the dialog still exists.
2828                     if (openDialogFragment != null) {
2829                         // Get a handle for the open dialog.
2830                         Dialog openDialog = openDialogFragment.getDialog();
2831
2832                         // Remove the incorrect lint warning below that the dialog might be null.
2833                         assert openDialog != null;
2834
2835                         // Get a handle for the file name edit text.
2836                         EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
2837
2838                         // Get the file name URI from the intent.
2839                         Uri fileNameUri = returnedIntent.getData();
2840
2841                         // Get the file name string from the URI.
2842                         String fileNameString = fileNameUri.toString();
2843
2844                         // Set the file name text.
2845                         fileNameEditText.setText(fileNameString);
2846
2847                         // Move the cursor to the end of the file name edit text.
2848                         fileNameEditText.setSelection(fileNameString.length());
2849                     }
2850                 }
2851                 break;
2852         }
2853     }
2854
2855     private void loadUrlFromTextBox() {
2856         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
2857         String unformattedUrlString = urlEditText.getText().toString().trim();
2858
2859         // Initialize the formatted URL string.
2860         String url = "";
2861
2862         // Check to see if the unformatted URL string is a valid URL.  Otherwise, convert it into a search.
2863         if (unformattedUrlString.startsWith("content://")) {  // This is a Content URL.
2864             // Load the entire content URL.
2865             url = unformattedUrlString;
2866         } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2867                 unformattedUrlString.startsWith("file://")) {  // This is a standard URL.
2868             // Add `https://` at the beginning if there is no protocol.  Otherwise the app will segfault.
2869             if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://")) {
2870                 unformattedUrlString = "https://" + unformattedUrlString;
2871             }
2872
2873             // Initialize the unformatted URL.
2874             URL unformattedUrl = null;
2875
2876             // Convert the unformatted URL string to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components.
2877             try {
2878                 unformattedUrl = new URL(unformattedUrlString);
2879             } catch (MalformedURLException e) {
2880                 e.printStackTrace();
2881             }
2882
2883             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
2884             String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
2885             String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
2886             String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
2887             String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
2888             String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
2889
2890             // Build the URI.
2891             Uri.Builder uri = new Uri.Builder();
2892             uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
2893
2894             // Decode the URI as a UTF-8 string in.
2895             try {
2896                 url = URLDecoder.decode(uri.build().toString(), "UTF-8");
2897             } catch (UnsupportedEncodingException exception) {
2898                 // Do nothing.  The formatted URL string will remain blank.
2899             }
2900         } else if (!unformattedUrlString.isEmpty()){  // This is not a URL, but rather a search string.
2901             // Create an encoded URL String.
2902             String encodedUrlString;
2903
2904             // Sanitize the search input.
2905             try {
2906                 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
2907             } catch (UnsupportedEncodingException exception) {
2908                 encodedUrlString = "";
2909             }
2910
2911             // Add the base search URL.
2912             url = searchURL + encodedUrlString;
2913         }
2914
2915         // 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.
2916         urlEditText.clearFocus();
2917
2918         // Make it so.
2919         loadUrl(currentWebView, url);
2920     }
2921
2922     private void loadUrl(NestedScrollWebView nestedScrollWebView, String url) {
2923         // Sanitize the URL.
2924         url = sanitizeUrl(url);
2925
2926         // Apply the domain settings and load the URL.
2927         applyDomainSettings(nestedScrollWebView, url, true, false, true);
2928     }
2929
2930     public void findPreviousOnPage(View view) {
2931         // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
2932         currentWebView.findNext(false);
2933     }
2934
2935     public void findNextOnPage(View view) {
2936         // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
2937         currentWebView.findNext(true);
2938     }
2939
2940     public void closeFindOnPage(View view) {
2941         // Get a handle for the views.
2942         Toolbar toolbar = findViewById(R.id.toolbar);
2943         LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2944         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2945
2946         // Delete the contents of `find_on_page_edittext`.
2947         findOnPageEditText.setText(null);
2948
2949         // Clear the highlighted phrases if the WebView is not null.
2950         if (currentWebView != null) {
2951             currentWebView.clearMatches();
2952         }
2953
2954         // Hide the find on page linear layout.
2955         findOnPageLinearLayout.setVisibility(View.GONE);
2956
2957         // Show the toolbar.
2958         toolbar.setVisibility(View.VISIBLE);
2959
2960         // Get a handle for the input method manager.
2961         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2962
2963         // Remove the lint warning below that the input method manager might be null.
2964         assert inputMethodManager != null;
2965
2966         // Hide the keyboard.
2967         inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
2968     }
2969
2970     @Override
2971     public void onApplyNewFontSize(DialogFragment dialogFragment) {
2972         // Remove the incorrect lint warning below that the dialog fragment might be null.
2973         assert dialogFragment != null;
2974
2975         // Get the dialog.
2976         Dialog dialog = dialogFragment.getDialog();
2977
2978         // Remove the incorrect lint warning below tha the dialog might be null.
2979         assert dialog != null;
2980
2981         // Get a handle for the font size edit text.
2982         EditText fontSizeEditText = dialog.findViewById(R.id.font_size_edittext);
2983
2984         // Initialize the new font size variable with the current font size.
2985         int newFontSize = currentWebView.getSettings().getTextZoom();
2986
2987         // Get the font size from the edit text.
2988         try {
2989             newFontSize = Integer.parseInt(fontSizeEditText.getText().toString());
2990         } catch (Exception exception) {
2991             // If the edit text does not contain a valid font size do nothing.
2992         }
2993
2994         // Apply the new font size.
2995         currentWebView.getSettings().setTextZoom(newFontSize);
2996     }
2997
2998     @Override
2999     public void onOpen(DialogFragment dialogFragment) {
3000         // Get the dialog.
3001         Dialog dialog = dialogFragment.getDialog();
3002
3003         // Remove the incorrect lint warning below that the dialog might be null.
3004         assert dialog != null;
3005
3006         // Get handles for the views.
3007         EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
3008         CheckBox mhtCheckBox = dialog.findViewById(R.id.mht_checkbox);
3009
3010         // Get the file path string.
3011         String openFilePath = fileNameEditText.getText().toString();
3012
3013         // Apply the domain settings.  This resets the favorite icon and removes any domain settings.
3014         applyDomainSettings(currentWebView, openFilePath, true, false, false);
3015
3016         // Open the file according to the type.
3017         if (mhtCheckBox.isChecked()) {  // Force opening of an MHT file.
3018             try {
3019                 // Get the MHT file input stream.
3020                 InputStream mhtFileInputStream = getContentResolver().openInputStream(Uri.parse(openFilePath));
3021
3022                 // Create a temporary MHT file.
3023                 File temporaryMhtFile = File.createTempFile("temporary_mht_file", ".mht", getCacheDir());
3024
3025                 // Get a file output stream for the temporary MHT file.
3026                 FileOutputStream temporaryMhtFileOutputStream = new FileOutputStream(temporaryMhtFile);
3027
3028                 // Create a transfer byte array.
3029                 byte[] transferByteArray = new byte[1024];
3030
3031                 // Create an integer to track the number of bytes read.
3032                 int bytesRead;
3033
3034                 // Copy the temporary MHT file input stream to the MHT output stream.
3035                 while ((bytesRead = mhtFileInputStream.read(transferByteArray)) > 0) {
3036                     temporaryMhtFileOutputStream.write(transferByteArray, 0, bytesRead);
3037                 }
3038
3039                 // Flush the temporary MHT file output stream.
3040                 temporaryMhtFileOutputStream.flush();
3041
3042                 // Close the streams.
3043                 temporaryMhtFileOutputStream.close();
3044                 mhtFileInputStream.close();
3045
3046                 // Load the temporary MHT file.
3047                 currentWebView.loadUrl(temporaryMhtFile.toString());
3048             } catch (Exception exception) {
3049                 // Display a snackbar.
3050                 Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
3051             }
3052         } else {  // Let the WebView handle opening of the file.
3053             // Open the file.
3054             currentWebView.loadUrl(openFilePath);
3055         }
3056     }
3057
3058     private void downloadUrlWithExternalApp(String url) {
3059         // Create a download intent.  Not specifying the action type will display the maximum number of options.
3060         Intent downloadIntent = new Intent();
3061
3062         // Set the URI and the mime type.
3063         downloadIntent.setDataAndType(Uri.parse(url), "text/html");
3064
3065         // Flag the intent to open in a new task.
3066         downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3067
3068         // Show the chooser.
3069         startActivity(Intent.createChooser(downloadIntent, getString(R.string.download_with_external_app)));
3070     }
3071
3072     public void onSaveUrl(@NonNull String originalUrlString, @NonNull String fileNameString, @NonNull DialogFragment dialogFragment) {
3073         // Store the URL.  This will be used in the save URL activity result launcher.
3074         if (originalUrlString.startsWith("data:")) {
3075             // Save the original URL.
3076             saveUrlString = originalUrlString;
3077         } else {
3078             // Get the dialog.
3079             Dialog dialog = dialogFragment.getDialog();
3080
3081             // Remove the incorrect lint warning below that the dialog might be null.
3082             assert dialog != null;
3083
3084             // Get a handle for the dialog URL edit text.
3085             EditText dialogUrlEditText = dialog.findViewById(R.id.url_edittext);
3086
3087             // Get the URL from the edit text, which may have been modified.
3088             saveUrlString = dialogUrlEditText.getText().toString();
3089         }
3090
3091         // Open the file picker.
3092         saveUrlActivityResultLauncher.launch(fileNameString);
3093     }
3094     
3095     // Remove the warning that `OnTouchListener()` needs to override `performClick()`, as the only purpose of setting the `OnTouchListener()` is to make it do nothing.
3096     @SuppressLint("ClickableViewAccessibility")
3097     private void initializeApp() {
3098         // Get a handle for the input method.
3099         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
3100
3101         // Remove the lint warning below that the input method manager might be null.
3102         assert inputMethodManager != null;
3103
3104         // Initialize the gray foreground color spans for highlighting the URLs.
3105         initialGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500));
3106         finalGrayColorSpan = new ForegroundColorSpan(getColor(R.color.gray_500));
3107
3108         // Get the current theme status.
3109         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
3110
3111         // Set the red color span according to the theme.
3112         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
3113             redColorSpan = new ForegroundColorSpan(getColor(R.color.red_a700));
3114         } else {
3115             redColorSpan = new ForegroundColorSpan(getColor(R.color.red_900));
3116         }
3117
3118         // Remove the formatting from the URL edit text when the user is editing the text.
3119         urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
3120             if (hasFocus) {  // The user is editing the URL text box.
3121                 // Remove the highlighting.
3122                 urlEditText.getText().removeSpan(redColorSpan);
3123                 urlEditText.getText().removeSpan(initialGrayColorSpan);
3124                 urlEditText.getText().removeSpan(finalGrayColorSpan);
3125             } else {  // The user has stopped editing the URL text box.
3126                 // Move to the beginning of the string.
3127                 urlEditText.setSelection(0);
3128
3129                 // Reapply the highlighting.
3130                 highlightUrlText();
3131             }
3132         });
3133
3134         // Set the go button on the keyboard to load the URL in `urlTextBox`.
3135         urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
3136             // If the event is a key-down event on the `enter` button, load the URL.
3137             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
3138                 // Load the URL into the mainWebView and consume the event.
3139                 loadUrlFromTextBox();
3140
3141                 // If the enter key was pressed, consume the event.
3142                 return true;
3143             } else {
3144                 // If any other key was pressed, do not consume the event.
3145                 return false;
3146             }
3147         });
3148
3149         // Create an Orbot status broadcast receiver.
3150         orbotStatusBroadcastReceiver = new BroadcastReceiver() {
3151             @Override
3152             public void onReceive(Context context, Intent intent) {
3153                 // Store the content of the status message in `orbotStatus`.
3154                 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
3155
3156                 // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
3157                 if ((orbotStatus != null) && orbotStatus.equals(ProxyHelper.ORBOT_STATUS_ON) && waitingForProxy) {
3158                     // Reset the waiting for proxy status.
3159                     waitingForProxy = false;
3160
3161                     // Get a list of the current fragments.
3162                     List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
3163
3164                     // Check each fragment to see if it is a waiting for proxy dialog.  Sometimes more than one is displayed.
3165                     for (int i = 0; i < fragmentList.size(); i++) {
3166                         // Get the fragment tag.
3167                         String fragmentTag = fragmentList.get(i).getTag();
3168
3169                         // Check to see if it is the waiting for proxy dialog.
3170                         if ((fragmentTag!= null) && fragmentTag.equals(getString(R.string.waiting_for_proxy_dialog))) {
3171                             // Dismiss the waiting for proxy dialog.
3172                             ((DialogFragment) fragmentList.get(i)).dismiss();
3173                         }
3174                     }
3175
3176                     // Reload existing URLs and load any URLs that are waiting for the proxy.
3177                     for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3178                         // Get the WebView tab fragment.
3179                         WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3180
3181                         // Get the fragment view.
3182                         View fragmentView = webViewTabFragment.getView();
3183
3184                         // Only process the WebViews if they exist.
3185                         if (fragmentView != null) {
3186                             // Get the nested scroll WebView from the tab fragment.
3187                             NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3188
3189                             // Get the waiting for proxy URL string.
3190                             String waitingForProxyUrlString = nestedScrollWebView.getWaitingForProxyUrlString();
3191
3192                             // Load the pending URL if it exists.
3193                             if (!waitingForProxyUrlString.isEmpty()) {  // A URL is waiting to be loaded.
3194                                 // Load the URL.
3195                                 loadUrl(nestedScrollWebView, waitingForProxyUrlString);
3196
3197                                 // Reset the waiting for proxy URL string.
3198                                 nestedScrollWebView.setWaitingForProxyUrlString("");
3199                             } else {  // No URL is waiting to be loaded.
3200                                 // Reload the existing URL.
3201                                 nestedScrollWebView.reload();
3202                             }
3203                         }
3204                     }
3205                 }
3206             }
3207         };
3208
3209         // Register the Orbot status broadcast receiver on `this` context.
3210         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
3211
3212         // Get handles for views that need to be modified.
3213         LinearLayout bookmarksHeaderLinearLayout = findViewById(R.id.bookmarks_header_linearlayout);
3214         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
3215         FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
3216         FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
3217         FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
3218         EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
3219
3220         // Update the web view pager every time a tab is modified.
3221         webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
3222             @Override
3223             public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
3224                 // Do nothing.
3225             }
3226
3227             @Override
3228             public void onPageSelected(int position) {
3229                 // Close the find on page bar if it is open.
3230                 closeFindOnPage(null);
3231
3232                 // Set the current WebView.
3233                 setCurrentWebView(position);
3234
3235                 // Select the corresponding tab if it does not match the currently selected page.  This will happen if the page was scrolled by creating a new tab.
3236                 if (tabLayout.getSelectedTabPosition() != position) {
3237                     // Wait until the new tab has been created.
3238                     tabLayout.post(() -> {
3239                         // Get a handle for the tab.
3240                         TabLayout.Tab tab = tabLayout.getTabAt(position);
3241
3242                         // Assert that the tab is not null.
3243                         assert tab != null;
3244
3245                         // Select the tab.
3246                         tab.select();
3247                     });
3248                 }
3249             }
3250
3251             @Override
3252             public void onPageScrollStateChanged(int state) {
3253                 // Do nothing.
3254             }
3255         });
3256
3257         // Display the View SSL Certificate dialog when the currently selected tab is reselected.
3258         tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
3259             @Override
3260             public void onTabSelected(TabLayout.Tab tab) {
3261                 // Select the same page in the view pager.
3262                 webViewPager.setCurrentItem(tab.getPosition());
3263             }
3264
3265             @Override
3266             public void onTabUnselected(TabLayout.Tab tab) {
3267                 // Do nothing.
3268             }
3269
3270             @Override
3271             public void onTabReselected(TabLayout.Tab tab) {
3272                 // Instantiate the View SSL Certificate dialog.
3273                 DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId(), currentWebView.getFavoriteOrDefaultIcon());
3274
3275                 // Display the View SSL Certificate dialog.
3276                 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
3277             }
3278         });
3279
3280         // Set a touch listener on the bookmarks header linear layout so that touches don't pass through to the button underneath.
3281         bookmarksHeaderLinearLayout.setOnTouchListener((view, motionEvent) -> {
3282             // Consume the touch.
3283             return true;
3284         });
3285
3286         // Set the launch bookmarks activity FAB to launch the bookmarks activity.
3287         launchBookmarksActivityFab.setOnClickListener(v -> {
3288             // Get a copy of the favorite icon bitmap.
3289             Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
3290
3291             // Create a favorite icon byte array output stream.
3292             ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3293
3294             // Convert the favorite icon bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
3295             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3296
3297             // Convert the favorite icon byte array stream to a byte array.
3298             byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3299
3300             // Create an intent to launch the bookmarks activity.
3301             Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
3302
3303             // Add the extra information to the intent.
3304             bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
3305             bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
3306             bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
3307             bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
3308
3309             // Make it so.
3310             startActivity(bookmarksIntent);
3311         });
3312
3313         // Set the create new bookmark folder FAB to display an alert dialog.
3314         createBookmarkFolderFab.setOnClickListener(v -> {
3315             // Create a create bookmark folder dialog.
3316             DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
3317
3318             // Show the create bookmark folder dialog.
3319             createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
3320         });
3321
3322         // Set the create new bookmark FAB to display an alert dialog.
3323         createBookmarkFab.setOnClickListener(view -> {
3324             // Instantiate the create bookmark dialog.
3325             DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
3326
3327             // Display the create bookmark dialog.
3328             createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
3329         });
3330
3331         // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
3332         findOnPageEditText.addTextChangedListener(new TextWatcher() {
3333             @Override
3334             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
3335                 // Do nothing.
3336             }
3337
3338             @Override
3339             public void onTextChanged(CharSequence s, int start, int before, int count) {
3340                 // Do nothing.
3341             }
3342
3343             @Override
3344             public void afterTextChanged(Editable s) {
3345                 // Search for the text in the WebView if it is not null.  Sometimes on resume after a period of non-use the WebView will be null.
3346                 if (currentWebView != null) {
3347                     currentWebView.findAllAsync(findOnPageEditText.getText().toString());
3348                 }
3349             }
3350         });
3351
3352         // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
3353         findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
3354             if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
3355                 // Hide the soft keyboard.
3356                 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3357
3358                 // Consume the event.
3359                 return true;
3360             } else {  // A different key was pressed.
3361                 // Do not consume the event.
3362                 return false;
3363             }
3364         });
3365
3366         // Implement swipe to refresh.
3367         swipeRefreshLayout.setOnRefreshListener(() -> {
3368             // Reload the website.
3369             currentWebView.reload();
3370         });
3371
3372         // Store the default progress view offsets for use later in `initializeWebView()`.
3373         defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
3374         defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
3375
3376         // Set the refresh color scheme according to the theme.
3377         swipeRefreshLayout.setColorSchemeResources(R.color.blue_text);
3378
3379         // Initialize a color background typed value.
3380         TypedValue colorBackgroundTypedValue = new TypedValue();
3381
3382         // Get the color background from the theme.
3383         getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
3384
3385         // Get the color background int from the typed value.
3386         int colorBackgroundInt = colorBackgroundTypedValue.data;
3387
3388         // Set the swipe refresh background color.
3389         swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
3390
3391         // The drawer titles identify the drawer layouts in accessibility mode.
3392         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
3393         drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
3394
3395         // Load the bookmarks folder.
3396         loadBookmarksFolder();
3397
3398         // Handle clicks on bookmarks.
3399         bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
3400             // Convert the id from long to int to match the format of the bookmarks database.
3401             int databaseId = (int) id;
3402
3403             // Get the bookmark cursor for this ID.
3404             Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
3405
3406             // Move the bookmark cursor to the first row.
3407             bookmarkCursor.moveToFirst();
3408
3409             // Act upon the bookmark according to the type.
3410             if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
3411                 // Store the new folder name in `currentBookmarksFolder`.
3412                 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
3413
3414                 // Load the new folder.
3415                 loadBookmarksFolder();
3416             } else {  // The selected bookmark is not a folder.
3417                 // Load the bookmark URL.
3418                 loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)));
3419
3420                 // Close the bookmarks drawer.
3421                 drawerLayout.closeDrawer(GravityCompat.END);
3422             }
3423
3424             // Close the `Cursor`.
3425             bookmarkCursor.close();
3426         });
3427
3428         bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
3429             // Convert the database ID from `long` to `int`.
3430             int databaseId = (int) id;
3431
3432             // Find out if the selected bookmark is a folder.
3433             boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
3434
3435             // Check to see if the bookmark is a folder.
3436             if (isFolder) {  // The bookmark is a folder.
3437                 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
3438                 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
3439
3440                 // Instantiate the edit folder bookmark dialog.
3441                 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
3442
3443                 // Show the edit folder bookmark dialog.
3444                 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
3445             } else {  // The bookmark is not a folder.
3446                 // Get the bookmark cursor for this ID.
3447                 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
3448
3449                 // Move the bookmark cursor to the first row.
3450                 bookmarkCursor.moveToFirst();
3451
3452                 // Load the bookmark in a new tab but do not switch to the tab or close the drawer.
3453                 addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
3454
3455                 // Display a snackbar.
3456                 Snackbar.make(drawerLayout, R.string.bookmark_opened_in_background, Snackbar.LENGTH_SHORT).show();
3457             }
3458
3459             // Consume the event.
3460             return true;
3461         });
3462
3463         // The drawer listener is used to update the navigation menu.
3464         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
3465             @Override
3466             public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
3467             }
3468
3469             @Override
3470             public void onDrawerOpened(@NonNull View drawerView) {
3471             }
3472
3473             @Override
3474             public void onDrawerClosed(@NonNull View drawerView) {
3475                 // Reset the drawer icon when the drawer is closed.  Otherwise, it is an arrow if the drawer is open when the app is restarted.
3476                 actionBarDrawerToggle.syncState();
3477             }
3478
3479             @Override
3480             public void onDrawerStateChanged(int newState) {
3481                 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) {  // A drawer is opening or closing.
3482                     // Update the navigation menu items if the WebView is not null.
3483                     if (currentWebView != null) {
3484                         navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
3485                         navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
3486                         navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
3487                         navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
3488
3489                         // Hide the keyboard (if displayed).
3490                         inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3491                     }
3492
3493                     // Clear the focus from from the URL text box.  This removes any text selection markers and context menus, which otherwise draw above the open drawers.
3494                     urlEditText.clearFocus();
3495
3496                     // Clear the focus from from the WebView if it is not null, which can happen if a user opens a drawer while the browser is being resumed.
3497                     if (currentWebView != null) {
3498                         // Clearing the focus from the WebView removes any text selection markers and context menus, which otherwise draw above the open drawers.
3499                         currentWebView.clearFocus();
3500                     }
3501                 }
3502             }
3503         });
3504
3505         // Inflate a bare WebView to get the default user agent.  It is not used to render content on the screen.
3506         @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
3507
3508         // Get a handle for the WebView.
3509         WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
3510
3511         // Store the default user agent.
3512         webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
3513
3514         // Destroy the bare WebView.
3515         bareWebView.destroy();
3516     }
3517
3518     private void applyAppSettings() {
3519         // Get a handle for the shared preferences.
3520         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3521
3522         // Store the values from the shared preferences in variables.
3523         incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
3524         sanitizeTrackingQueries = sharedPreferences.getBoolean(getString(R.string.tracking_queries_key), true);
3525         sanitizeAmpRedirects = sharedPreferences.getBoolean(getString(R.string.amp_redirects_key), true);
3526         proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
3527         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
3528         downloadWithExternalApp = sharedPreferences.getBoolean(getString(R.string.download_with_external_app_key), false);
3529         hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
3530         scrollAppBar = sharedPreferences.getBoolean(getString(R.string.scroll_app_bar_key), true);
3531
3532         // Apply the saved proxy mode if the app has been restarted.
3533         if (savedProxyMode != null) {
3534             // Apply the saved proxy mode.
3535             proxyMode = savedProxyMode;
3536
3537             // Reset the saved proxy mode.
3538             savedProxyMode = null;
3539         }
3540
3541         // Get the search string.
3542         String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
3543
3544         // Set the search string.
3545         if (searchString.equals("Custom URL")) {  // A custom search string is used.
3546             searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
3547         } else {  // A custom search string is not used.
3548             searchURL = searchString;
3549         }
3550
3551         // Apply the proxy.
3552         applyProxy(false);
3553
3554         // Adjust the layout and scrolling parameters according to the position of the app bar.
3555         if (bottomAppBar) {  // The app bar is on the bottom.
3556             // Adjust the UI.
3557             if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
3558                 // Reset the WebView padding to fill the available space.
3559                 swipeRefreshLayout.setPadding(0, 0, 0, 0);
3560             } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
3561                 // Move the WebView above the app bar layout.
3562                 swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
3563
3564                 // Show the app bar if it is scrolled off the screen.
3565                 if (appBarLayout.getTranslationY() != 0) {
3566                     // Animate the bottom app bar onto the screen.
3567                     objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
3568
3569                     // Make it so.
3570                     objectAnimator.start();
3571                 }
3572             }
3573         } else {  // The app bar is on the top.
3574             // Get the current layout parameters.  Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
3575             CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
3576             AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
3577             AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
3578             AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
3579
3580             // Add the scrolling behavior to the layout parameters.
3581             if (scrollAppBar) {
3582                 // Enable scrolling of the app bar.
3583                 swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
3584                 toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3585                 findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3586                 tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3587             } else {
3588                 // Disable scrolling of the app bar.
3589                 swipeRefreshLayoutParams.setBehavior(null);
3590                 toolbarLayoutParams.setScrollFlags(0);
3591                 findOnPageLayoutParams.setScrollFlags(0);
3592                 tabsLayoutParams.setScrollFlags(0);
3593
3594                 // Expand the app bar if it is currently collapsed.
3595                 appBarLayout.setExpanded(true);
3596             }
3597
3598             // Set the app bar scrolling for each WebView.
3599             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3600                 // Get the WebView tab fragment.
3601                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3602
3603                 // Get the fragment view.
3604                 View fragmentView = webViewTabFragment.getView();
3605
3606                 // Only modify the WebViews if they exist.
3607                 if (fragmentView != null) {
3608                     // Get the nested scroll WebView from the tab fragment.
3609                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3610
3611                     // Set the app bar scrolling.
3612                     nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
3613                 }
3614             }
3615         }
3616
3617         // Update the full screen browsing mode settings.
3618         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
3619             // Update the visibility of the app bar, which might have changed in the settings.
3620             if (hideAppBar) {
3621                 // Hide the tab linear layout.
3622                 tabsLinearLayout.setVisibility(View.GONE);
3623
3624                 // Hide the action bar.
3625                 actionBar.hide();
3626             } else {
3627                 // Show the tab linear layout.
3628                 tabsLinearLayout.setVisibility(View.VISIBLE);
3629
3630                 // Show the action bar.
3631                 actionBar.show();
3632             }
3633
3634             /* Hide the system bars.
3635              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
3636              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
3637              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
3638              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
3639              */
3640             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
3641                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
3642         } else {  // Privacy Browser is not in full screen browsing mode.
3643             // Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
3644             inFullScreenBrowsingMode = false;
3645
3646             // Show the tab linear layout.
3647             tabsLinearLayout.setVisibility(View.VISIBLE);
3648
3649             // Show the action bar.
3650             actionBar.show();
3651
3652             // Remove the `SYSTEM_UI` flags from the root frame layout.
3653             rootFrameLayout.setSystemUiVisibility(0);
3654         }
3655     }
3656
3657     @Override
3658     public void navigateHistory(@NonNull String url, int steps) {
3659         // Apply the domain settings.
3660         applyDomainSettings(currentWebView, url, false, false, false);
3661
3662         // Load the history entry.
3663         currentWebView.goBackOrForward(steps);
3664     }
3665
3666     @Override
3667     public void pinnedErrorGoBack() {
3668         // Get the current web back forward list.
3669         WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
3670
3671         // Get the previous entry URL.
3672         String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
3673
3674         // Apply the domain settings.
3675         applyDomainSettings(currentWebView, previousUrl, false, false, false);
3676
3677         // Go back.
3678         currentWebView.goBack();
3679     }
3680
3681     // `reloadWebsite` is used if returning from the Domains activity.  Otherwise JavaScript might not function correctly if it is newly enabled.
3682     @SuppressLint("SetJavaScriptEnabled")
3683     private void applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite, boolean loadUrl) {
3684         // Store the current URL.
3685         nestedScrollWebView.setCurrentUrl(url);
3686
3687         // Parse the URL into a URI.
3688         Uri uri = Uri.parse(url);
3689
3690         // Extract the domain from `uri`.
3691         String newHostName = uri.getHost();
3692
3693         // Strings don't like to be null.
3694         if (newHostName == null) {
3695             newHostName = "";
3696         }
3697
3698         // Apply the domain settings if a new domain is being loaded or if the new domain is blank.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
3699         if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName) || newHostName.equals("")) {
3700             // Set the new host name as the current domain name.
3701             nestedScrollWebView.setCurrentDomainName(newHostName);
3702
3703             // Reset the ignoring of pinned domain information.
3704             nestedScrollWebView.setIgnorePinnedDomainInformation(false);
3705
3706             // Clear any pinned SSL certificate or IP addresses.
3707             nestedScrollWebView.clearPinnedSslCertificate();
3708             nestedScrollWebView.setPinnedIpAddresses("");
3709
3710             // Reset the favorite icon if specified.
3711             if (resetTab) {
3712                 // Initialize the favorite icon.
3713                 nestedScrollWebView.initializeFavoriteIcon();
3714
3715                 // Get the current page position.
3716                 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
3717
3718                 // Get the corresponding tab.
3719                 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
3720
3721                 // Update the tab if it isn't null, which sometimes happens when restarting from the background.
3722                 if (tab != null) {
3723                     // Get the tab custom view.
3724                     View tabCustomView = tab.getCustomView();
3725
3726                     // Remove the warning below that the tab custom view might be null.
3727                     assert tabCustomView != null;
3728
3729                     // Get the tab views.
3730                     ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
3731                     TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
3732
3733                     // Set the default favorite icon as the favorite icon for this tab.
3734                     tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
3735
3736                     // Set the loading title text.
3737                     tabTitleTextView.setText(R.string.loading);
3738                 }
3739             }
3740
3741             // Get a full domain name cursor.
3742             Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
3743
3744             // Initialize `domainSettingsSet`.
3745             Set<String> domainSettingsSet = new HashSet<>();
3746
3747             // Get the domain name column index.
3748             int domainNameColumnIndex = domainNameCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME);
3749
3750             // Populate the domain settings set.
3751             for (int i = 0; i < domainNameCursor.getCount(); i++) {
3752                 // Move the domains cursor to the current row.
3753                 domainNameCursor.moveToPosition(i);
3754
3755                 // Store the domain name in the domain settings set.
3756                 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
3757             }
3758
3759             // Close the domain name cursor.
3760             domainNameCursor.close();
3761
3762             // Initialize the domain name in database variable.
3763             String domainNameInDatabase = null;
3764
3765             // Check the hostname against the domain settings set.
3766             if (domainSettingsSet.contains(newHostName)) {  // The hostname is contained in the domain settings set.
3767                 // Record the domain name in the database.
3768                 domainNameInDatabase = newHostName;
3769
3770                 // Set the domain settings applied tracker to true.
3771                 nestedScrollWebView.setDomainSettingsApplied(true);
3772             } else {  // The hostname is not contained in the domain settings set.
3773                 // Set the domain settings applied tracker to false.
3774                 nestedScrollWebView.setDomainSettingsApplied(false);
3775             }
3776
3777             // Check all the subdomains of the host name against wildcard domains in the domain cursor.
3778             while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) {  // Stop checking if domain settings are already applied or there are no more `.` in the hostname.
3779                 if (domainSettingsSet.contains("*." + newHostName)) {  // Check the host name prepended by `*.`.
3780                     // Set the domain settings applied tracker to true.
3781                     nestedScrollWebView.setDomainSettingsApplied(true);
3782
3783                     // Store the applied domain names as it appears in the database.
3784                     domainNameInDatabase = "*." + newHostName;
3785                 }
3786
3787                 // Strip out the lowest subdomain of of the host name.
3788                 newHostName = newHostName.substring(newHostName.indexOf(".") + 1);
3789             }
3790
3791
3792             // Get a handle for the shared preferences.
3793             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3794
3795             // Store the general preference information.
3796             boolean defaultXRequestedWithHeader = sharedPreferences.getBoolean(getString(R.string.x_requested_with_header_key), true);
3797             String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
3798             String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
3799             boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
3800             String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
3801             boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
3802             boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
3803
3804             // Get the WebView theme entry values string array.
3805             String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values);
3806
3807             // Get a handle for the cookie manager.
3808             CookieManager cookieManager = CookieManager.getInstance();
3809
3810             // Initialize the user agent array adapter and string array.
3811             ArrayAdapter<CharSequence> userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
3812             String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
3813
3814             if (nestedScrollWebView.getDomainSettingsApplied()) {  // The url has custom domain settings.
3815                 // Remove the incorrect lint warning below that the domain name in database might be null.
3816                 assert domainNameInDatabase != null;
3817
3818                 // Get a cursor for the current host.
3819                 Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
3820
3821                 // Move to the first position.
3822                 currentDomainSettingsCursor.moveToFirst();
3823
3824                 // Get the settings from the cursor.
3825                 nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ID)));
3826                 nestedScrollWebView.getSettings().setJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
3827                 nestedScrollWebView.setAcceptCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES)) == 1);
3828                 nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
3829                 // Form data can be removed once the minimum API >= 26.
3830                 boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
3831                 nestedScrollWebView.setEasyListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
3832                 nestedScrollWebView.setEasyPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
3833                 nestedScrollWebView.setFanboysAnnoyanceListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
3834                         DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
3835                 nestedScrollWebView.setFanboysSocialBlockingListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
3836                         DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
3837                 nestedScrollWebView.setUltraListEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST)) == 1);
3838                 nestedScrollWebView.setUltraPrivacyEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
3839                 nestedScrollWebView.setBlockAllThirdPartyRequests(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(
3840                         DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
3841                 String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT));
3842                 int xRequestedWithHeaderInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.X_REQUESTED_WITH_HEADER));
3843                 int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE));
3844                 int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
3845                 int webViewThemeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME));
3846                 int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT));
3847                 int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES));
3848                 boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
3849                 String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
3850                 String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
3851                 String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
3852                 String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
3853                 String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
3854                 String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
3855                 Date pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE)));
3856                 Date pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE)));
3857                 boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
3858                 String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES));
3859
3860                 // Close the current host domain settings cursor.
3861                 currentDomainSettingsCursor.close();
3862
3863                 // If there is a pinned SSL certificate, store it in the WebView.
3864                 if (pinnedSslCertificate) {
3865                     nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName,
3866                             pinnedSslStartDate, pinnedSslEndDate);
3867                 }
3868
3869                 // If there is a pinned IP address, store it in the WebView.
3870                 if (pinnedIpAddresses) {
3871                     nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses);
3872                 }
3873
3874                 // Apply the cookie domain settings.
3875                 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
3876
3877                 // Apply the form data setting if the API < 26.
3878                 if (Build.VERSION.SDK_INT < 26) {
3879                     nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
3880                 }
3881
3882                 // Set the X-Requested-With header.
3883                 switch (xRequestedWithHeaderInt) {
3884                     case DomainsDatabaseHelper.SYSTEM_DEFAULT:
3885                         if (defaultXRequestedWithHeader)
3886                             nestedScrollWebView.setXRequestedWithHeader();
3887                         else
3888                             nestedScrollWebView.resetXRequestedWithHeader();
3889                         break;
3890
3891                     case DomainsDatabaseHelper.ENABLED:
3892                         nestedScrollWebView.setXRequestedWithHeader();
3893                         break;
3894
3895                     case DomainsDatabaseHelper.DISABLED:
3896                         nestedScrollWebView.resetXRequestedWithHeader();
3897                         break;
3898                 }
3899
3900                 // Apply the font size.
3901                 try {  // Try the specified font size to see if it is valid.
3902                     if (fontSize == 0) {  // Apply the default font size.
3903                             // Try to set the font size from the value in the app settings.
3904                             nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
3905                     } else {  // Apply the font size from domain settings.
3906                         nestedScrollWebView.getSettings().setTextZoom(fontSize);
3907                     }
3908                 } catch (Exception exception) {  // The specified font size is invalid
3909                     // Set the font size to be 100%
3910                     nestedScrollWebView.getSettings().setTextZoom(100);
3911                 }
3912
3913                 // Set the user agent.
3914                 if (userAgentName.equals(getString(R.string.system_default_user_agent))) {  // Use the system default user agent.
3915                     // Get the array position of the default user agent name.
3916                     int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
3917
3918                     // Set the user agent according to the system default.
3919                     switch (defaultUserAgentArrayPosition) {
3920                         case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
3921                             // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
3922                             nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
3923                             break;
3924
3925                         case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
3926                             // Set the user agent to `""`, which uses the default value.
3927                             nestedScrollWebView.getSettings().setUserAgentString("");
3928                             break;
3929
3930                         case SETTINGS_CUSTOM_USER_AGENT:
3931                             // Set the default custom user agent.
3932                             nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
3933                             break;
3934
3935                         default:
3936                             // Get the user agent string from the user agent data array
3937                             nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
3938                     }
3939                 } else {  // Set the user agent according to the stored name.
3940                     // Get the array position of the user agent name.
3941                     int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
3942
3943                     switch (userAgentArrayPosition) {
3944                         case UNRECOGNIZED_USER_AGENT:  // The user agent name contains a custom user agent.
3945                             nestedScrollWebView.getSettings().setUserAgentString(userAgentName);
3946                             break;
3947
3948                         case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
3949                             // Set the user agent to `""`, which uses the default value.
3950                             nestedScrollWebView.getSettings().setUserAgentString("");
3951                             break;
3952
3953                         default:
3954                             // Get the user agent string from the user agent data array.
3955                             nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
3956                     }
3957                 }
3958
3959                 // Set swipe to refresh.
3960                 switch (swipeToRefreshInt) {
3961                     case DomainsDatabaseHelper.SYSTEM_DEFAULT:
3962                         // Store the swipe to refresh status in the nested scroll WebView.
3963                         nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
3964
3965                         // Update the swipe refresh layout.
3966                         if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
3967                             // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
3968                             swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
3969                         } else {  // Swipe to refresh is disabled.
3970                             // Disable the swipe refresh layout.
3971                             swipeRefreshLayout.setEnabled(false);
3972                         }
3973                         break;
3974
3975                     case DomainsDatabaseHelper.ENABLED:
3976                         // Store the swipe to refresh status in the nested scroll WebView.
3977                         nestedScrollWebView.setSwipeToRefresh(true);
3978
3979                         // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
3980                         swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
3981                         break;
3982
3983                     case DomainsDatabaseHelper.DISABLED:
3984                         // Store the swipe to refresh status in the nested scroll WebView.
3985                         nestedScrollWebView.setSwipeToRefresh(false);
3986
3987                         // Disable swipe to refresh.
3988                         swipeRefreshLayout.setEnabled(false);
3989                         break;
3990                 }
3991
3992                 // Check to see if WebView themes are supported.
3993                 if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
3994                     // Set the WebView theme.
3995                     switch (webViewThemeInt) {
3996                         case DomainsDatabaseHelper.SYSTEM_DEFAULT:
3997                             // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
3998                             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
3999                                 // Turn off algorithmic darkening.
4000                                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
4001                             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
4002                                 // Turn on algorithmic darkening.
4003                                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
4004                             } else {  // The system default theme is selected.
4005                                 // Get the current system theme status.
4006                                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4007
4008                                 // Set the algorithmic darkening according to the current system theme status.
4009                                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES));
4010                             }
4011                             break;
4012
4013                         case DomainsDatabaseHelper.LIGHT_THEME:
4014                             // Turn off algorithmic darkening.
4015                             WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
4016                             break;
4017
4018                         case DomainsDatabaseHelper.DARK_THEME:
4019                             // Turn on algorithmic darkening.
4020                             WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
4021                             break;
4022                     }
4023                 } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
4024                     // Set the WebView theme.
4025                     switch (webViewThemeInt) {
4026                         case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4027                             // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
4028                             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
4029                                 // Turn off the WebView dark mode.
4030                                 //noinspection deprecation
4031                                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4032                             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
4033                                 // Turn on the WebView dark mode.
4034                                 //noinspection deprecation
4035                                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4036                             } else {  // The system default theme is selected.
4037                                 // Get the current system theme status.
4038                                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4039
4040                                 // Set the WebView theme according to the current system theme status.
4041                                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
4042                                     // Turn off the WebView dark mode.
4043                                     //noinspection deprecation
4044                                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4045                                 } else {  // The system is in night mode.
4046                                     // Turn on the WebView dark mode.
4047                                     //noinspection deprecation
4048                                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4049                                 }
4050                             }
4051                             break;
4052
4053                         case DomainsDatabaseHelper.LIGHT_THEME:
4054                             // Turn off the WebView dark mode.
4055                             //noinspection deprecation
4056                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4057                             break;
4058
4059                         case DomainsDatabaseHelper.DARK_THEME:
4060                             // Turn on the WebView dark mode.
4061                             //noinspection deprecation
4062                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4063                             break;
4064                     }
4065                 }
4066
4067                 // Set the viewport.
4068                 switch (wideViewportInt) {
4069                     case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4070                         nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
4071                         break;
4072
4073                     case DomainsDatabaseHelper.ENABLED:
4074                         nestedScrollWebView.getSettings().setUseWideViewPort(true);
4075                         break;
4076
4077                     case DomainsDatabaseHelper.DISABLED:
4078                         nestedScrollWebView.getSettings().setUseWideViewPort(false);
4079                         break;
4080                 }
4081
4082                 // Set the loading of webpage images.
4083                 switch (displayWebpageImagesInt) {
4084                     case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4085                         nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4086                         break;
4087
4088                     case DomainsDatabaseHelper.ENABLED:
4089                         nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true);
4090                         break;
4091
4092                     case DomainsDatabaseHelper.DISABLED:
4093                         nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false);
4094                         break;
4095                 }
4096
4097                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
4098                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null));
4099             } else {  // The new URL does not have custom domain settings.  Load the defaults.
4100                 // Store the values from the shared preferences.
4101                 nestedScrollWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
4102                 nestedScrollWebView.setAcceptCookies(sharedPreferences.getBoolean(getString(R.string.cookies_key), false));
4103                 nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
4104                 boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false);  // Form data can be removed once the minimum API >= 26.
4105                 nestedScrollWebView.setEasyListEnabled(sharedPreferences.getBoolean("easylist", true));
4106                 nestedScrollWebView.setEasyPrivacyEnabled(sharedPreferences.getBoolean("easyprivacy", true));
4107                 nestedScrollWebView.setFanboysAnnoyanceListEnabled(sharedPreferences.getBoolean("fanboys_annoyance_list", true));
4108                 nestedScrollWebView.setFanboysSocialBlockingListEnabled(sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
4109                 nestedScrollWebView.setUltraListEnabled(sharedPreferences.getBoolean("ultralist", true));
4110                 nestedScrollWebView.setUltraPrivacyEnabled(sharedPreferences.getBoolean("ultraprivacy", true));
4111                 nestedScrollWebView.setBlockAllThirdPartyRequests(sharedPreferences.getBoolean("block_all_third_party_requests", false));
4112
4113                 // Apply the default cookie setting.
4114                 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptCookies());
4115
4116                 // Apply the default font size setting.
4117                 try {
4118                     // Try to set the font size from the value in the app settings.
4119                     nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
4120                 } catch (Exception exception) {
4121                     // If the app settings value is invalid, set the font size to 100%.
4122                     nestedScrollWebView.getSettings().setTextZoom(100);
4123                 }
4124
4125                 // Apply the form data setting if the API < 26.
4126                 if (Build.VERSION.SDK_INT < 26) {
4127                     nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
4128                 }
4129
4130                 // Store the X-Requested-With header status in the nested scroll WebView.
4131                 if (defaultXRequestedWithHeader)
4132                     nestedScrollWebView.setXRequestedWithHeader();
4133                 else
4134                     nestedScrollWebView.resetXRequestedWithHeader();
4135
4136                 // Store the swipe to refresh status in the nested scroll WebView.
4137                 nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
4138
4139                 // Update the swipe refresh layout.
4140                 if (defaultSwipeToRefresh) {  // Swipe to refresh is enabled.
4141                     // Only enable the swipe refresh layout if the WebView is scrolled to the top.  It is updated every time the scroll changes.
4142                     swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
4143                 } else {  // Swipe to refresh is disabled.
4144                     // Disable the swipe refresh layout.
4145                     swipeRefreshLayout.setEnabled(false);
4146                 }
4147
4148                 // Reset the pinned variables.
4149                 nestedScrollWebView.setDomainSettingsDatabaseId(-1);
4150
4151                 // Get the array position of the user agent name.
4152                 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4153
4154                 // Set the user agent.
4155                 switch (userAgentArrayPosition) {
4156                     case UNRECOGNIZED_USER_AGENT:  // The default user agent name is not on the canonical list.
4157                         // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4158                         nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
4159                         break;
4160
4161                     case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4162                         // Set the user agent to `""`, which uses the default value.
4163                         nestedScrollWebView.getSettings().setUserAgentString("");
4164                         break;
4165
4166                     case SETTINGS_CUSTOM_USER_AGENT:
4167                         // Set the default custom user agent.
4168                         nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
4169                         break;
4170
4171                     default:
4172                         // Get the user agent string from the user agent data array
4173                         nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4174                 }
4175
4176                 // Apply the WebView theme if supported by the installed WebView.
4177                 if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
4178                     // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
4179                     if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // the light theme is selected.
4180                         // Turn off algorithmic darkening.
4181                         WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
4182                     } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
4183                         // Turn on algorithmic darkening.
4184                         WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
4185                     } else {  // The system default theme is selected.
4186                         // Get the current system theme status.
4187                         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4188
4189                         // Set the algorithmic darkening according to the current system theme status.
4190                         WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), currentThemeStatus == Configuration.UI_MODE_NIGHT_YES);
4191                     }
4192                 } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
4193                     // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
4194                     if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
4195                         // Turn off the WebView dark mode.
4196                         //noinspection deprecation
4197                         WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4198                     } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
4199                         // Turn on the WebView dark mode.
4200                         //noinspection deprecation
4201                         WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4202                     } else {  // The system default theme is selected.
4203                         // Get the current system theme status.
4204                         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4205
4206                         // Set the WebView theme according to the current system theme status.
4207                         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
4208                             // Turn off the WebView dark mode.
4209                             //noinspection deprecation
4210                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
4211                         } else {  // The system is in night mode.
4212                             // Turn on the WebView dark mode.
4213                             //noinspection deprecation
4214                             WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
4215                         }
4216                     }
4217                 }
4218
4219                 // Set the viewport.
4220                 nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
4221
4222                 // Set the loading of webpage images.
4223                 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4224
4225                 // Set a transparent background on the URL relative layout.
4226                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
4227             }
4228
4229             // Close the domains database helper.
4230             domainsDatabaseHelper.close();
4231
4232             // Update the privacy icons.
4233             updatePrivacyIcons(true);
4234         }
4235
4236         // Reload the website if returning from the Domains activity.
4237         if (reloadWebsite) {
4238             nestedScrollWebView.reload();
4239         }
4240
4241         // Load the URL if directed.  This makes sure that the domain settings are properly loaded before the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
4242         if (loadUrl) {
4243             nestedScrollWebView.loadUrl(url, nestedScrollWebView.getXRequestedWithHeader());
4244         }
4245     }
4246
4247     private void applyProxy(boolean reloadWebViews) {
4248         // Set the proxy according to the mode.
4249         proxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode);
4250
4251         // Reset the waiting for proxy tracker.
4252         waitingForProxy = false;
4253
4254         // Get the current theme status.
4255         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
4256
4257         // Update the user interface and reload the WebViews if requested.
4258         switch (proxyMode) {
4259             case ProxyHelper.NONE:
4260                 // Initialize a color background typed value.
4261                 TypedValue colorBackgroundTypedValue = new TypedValue();
4262
4263                 // Get the color background from the theme.
4264                 getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
4265
4266                 // Get the color background int from the typed value.
4267                 int colorBackgroundInt = colorBackgroundTypedValue.data;
4268
4269                 // Set the default app bar layout background.
4270                 appBarLayout.setBackgroundColor(colorBackgroundInt);
4271                 break;
4272
4273             case ProxyHelper.TOR:
4274                 // Set the app bar background to indicate proxying through Orbot is enabled.
4275                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4276                     appBarLayout.setBackgroundResource(R.color.blue_50);
4277                 } else {
4278                     appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4279                 }
4280
4281                 // Check to see if Orbot is installed.
4282                 try {
4283                     // Get the package manager.
4284                     PackageManager packageManager = getPackageManager();
4285
4286                     // Check to see if Orbot is in the list.  This will throw an error and drop to the catch section if it isn't installed.
4287                     packageManager.getPackageInfo("org.torproject.android", 0);
4288
4289                     // Check to see if the proxy is ready.
4290                     if (!orbotStatus.equals(ProxyHelper.ORBOT_STATUS_ON)) {  // Orbot is not ready.
4291                         // Set the waiting for proxy status.
4292                         waitingForProxy = true;
4293
4294                         // Show the waiting for proxy dialog if it isn't already displayed.
4295                         if (getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) {
4296                             // Get a handle for the waiting for proxy alert dialog.
4297                             DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog();
4298
4299                             // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
4300                             try {
4301                                 // Show the waiting for proxy alert dialog.
4302                                 waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
4303                             } catch (Exception waitingForTorException) {
4304                                 // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
4305                                 pendingDialogsArrayList.add(new PendingDialog(waitingForProxyDialogFragment, getString(R.string.waiting_for_proxy_dialog)));
4306                             }
4307                         }
4308                     }
4309                 } catch (PackageManager.NameNotFoundException exception) {  // Orbot is not installed.
4310                     // Show the Orbot not installed dialog if it is not already displayed.
4311                     if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
4312                         // Get a handle for the Orbot not installed alert dialog.
4313                         DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
4314
4315                         // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
4316                         try {
4317                             // Display the Orbot not installed alert dialog.
4318                             orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
4319                         } catch (Exception orbotNotInstalledException) {
4320                             // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
4321                             pendingDialogsArrayList.add(new PendingDialog(orbotNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
4322                         }
4323                     }
4324                 }
4325                 break;
4326
4327             case ProxyHelper.I2P:
4328                 // Set the app bar background to indicate proxying through Orbot is enabled.
4329                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4330                     appBarLayout.setBackgroundResource(R.color.blue_50);
4331                 } else {
4332                     appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4333                 }
4334
4335                 // Check to see if I2P is installed.
4336                 try {
4337                     // Get the package manager.
4338                     PackageManager packageManager = getPackageManager();
4339
4340                     // Check to see if I2P is in the list.  This will throw an error and drop to the catch section if it isn't installed.
4341                     packageManager.getPackageInfo("net.i2p.android.router", 0);
4342                 } catch (PackageManager.NameNotFoundException exception) {  // I2P is not installed.
4343                     // Sow the I2P not installed dialog if it is not already displayed.
4344                     if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
4345                         // Get a handle for the waiting for proxy alert dialog.
4346                         DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
4347
4348                         // Try to show the dialog.  Sometimes the window is not yet active if returning from Settings.
4349                         try {
4350                             // Display the I2P not installed alert dialog.
4351                             i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
4352                         } catch (Exception i2pNotInstalledException) {
4353                             // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
4354                             pendingDialogsArrayList.add(new PendingDialog(i2pNotInstalledDialogFragment, getString(R.string.proxy_not_installed_dialog)));
4355                         }
4356                     }
4357                 }
4358                 break;
4359
4360             case ProxyHelper.CUSTOM:
4361                 // Set the app bar background to indicate proxying through Orbot is enabled.
4362                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
4363                     appBarLayout.setBackgroundResource(R.color.blue_50);
4364                 } else {
4365                     appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4366                 }
4367                 break;
4368         }
4369
4370         // Reload the WebViews if requested and not waiting for the proxy.
4371         if (reloadWebViews && !waitingForProxy) {
4372             // Reload the WebViews.
4373             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4374                 // Get the WebView tab fragment.
4375                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4376
4377                 // Get the fragment view.
4378                 View fragmentView = webViewTabFragment.getView();
4379
4380                 // Only reload the WebViews if they exist.
4381                 if (fragmentView != null) {
4382                     // Get the nested scroll WebView from the tab fragment.
4383                     NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4384
4385                     // Reload the WebView.
4386                     nestedScrollWebView.reload();
4387                 }
4388             }
4389         }
4390     }
4391
4392     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4393         // Only update the privacy icons if the options menu and the current WebView have already been populated.
4394         if ((optionsMenu != null) && (currentWebView != null)) {
4395             // Update the privacy icon.
4396             if (currentWebView.getSettings().getJavaScriptEnabled()) {  // JavaScript is enabled.
4397                 optionsPrivacyMenuItem.setIcon(R.drawable.javascript_enabled);
4398             } else if (currentWebView.getAcceptCookies()) {  // JavaScript is disabled but cookies are enabled.
4399                 optionsPrivacyMenuItem.setIcon(R.drawable.warning);
4400             } else {  // All the dangerous features are disabled.
4401                 optionsPrivacyMenuItem.setIcon(R.drawable.privacy_mode);
4402             }
4403
4404             // Update the cookies icon.
4405             if (currentWebView.getAcceptCookies()) {
4406                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4407             } else {
4408                 optionsCookiesMenuItem.setIcon(R.drawable.cookies_disabled);
4409             }
4410
4411             // Update the refresh icon.
4412             if (optionsRefreshMenuItem.getTitle() == getString(R.string.refresh)) {  // The refresh icon is displayed.
4413                 // Set the icon.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
4414                 optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled);
4415             } else {  // The stop icon is displayed.
4416                 // Set the icon.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
4417                 optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
4418             }
4419
4420             // `invalidateOptionsMenu()` calls `onPrepareOptionsMenu()` and redraws the icons in the app bar.
4421             if (runInvalidateOptionsMenu) {
4422                 invalidateOptionsMenu();
4423             }
4424         }
4425     }
4426
4427     private void highlightUrlText() {
4428         // Only highlight the URL text if the box is not currently selected.
4429         if (!urlEditText.hasFocus()) {
4430             // Get the URL string.
4431             String urlString = urlEditText.getText().toString();
4432
4433             // Highlight the URL according to the protocol.
4434             if (urlString.startsWith("file://") || urlString.startsWith("content://")) {  // This is a file or content URL.
4435                 // De-emphasize everything before the file name.
4436                 urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4437             } else {  // This is a web URL.
4438                 // Get the index of the `/` immediately after the domain name.
4439                 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4440
4441                 // Create a base URL string.
4442                 String baseUrl;
4443
4444                 // Get the base URL.
4445                 if (endOfDomainName > 0) {  // There is at least one character after the base URL.
4446                     // Get the base URL.
4447                     baseUrl = urlString.substring(0, endOfDomainName);
4448                 } else {  // There are no characters after the base URL.
4449                     // Set the base URL to be the entire URL string.
4450                     baseUrl = urlString;
4451                 }
4452
4453                 // Get the index of the last `.` in the domain.
4454                 int lastDotIndex = baseUrl.lastIndexOf(".");
4455
4456                 // Get the index of the penultimate `.` in the domain.
4457                 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4458
4459                 // Markup the beginning of the URL.
4460                 if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
4461                     urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4462
4463                     // De-emphasize subdomains.
4464                     if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
4465                         urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4466                     }
4467                 } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
4468                     if (penultimateDotIndex > 0) {  // There is more than one subdomain in the domain name.
4469                         // De-emphasize the protocol and the additional subdomains.
4470                         urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4471                     } else {  // There is only one subdomain in the domain name.
4472                         // De-emphasize only the protocol.
4473                         urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4474                     }
4475                 }
4476
4477                 // De-emphasize the text after the domain name.
4478                 if (endOfDomainName > 0) {
4479                     urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4480                 }
4481             }
4482         }
4483     }
4484
4485     private void loadBookmarksFolder() {
4486         // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4487         bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4488
4489         // Populate the bookmarks cursor adapter.
4490         bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4491             @Override
4492             public View newView(Context context, Cursor cursor, ViewGroup parent) {
4493                 // Inflate the individual item layout.
4494                 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4495             }
4496
4497             @Override
4498             public void bindView(View view, Context context, Cursor cursor) {
4499                 // Get handles for the views.
4500                 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4501                 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4502
4503                 // Get the favorite icon byte array from the cursor.
4504                 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.FAVORITE_ICON));
4505
4506                 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4507                 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4508
4509                 // Display the bitmap in `bookmarkFavoriteIcon`.
4510                 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4511
4512                 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4513                 String bookmarkNameString = cursor.getString(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.BOOKMARK_NAME));
4514                 bookmarkNameTextView.setText(bookmarkNameString);
4515
4516                 // Make the font bold for folders.
4517                 if (cursor.getInt(cursor.getColumnIndexOrThrow(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4518                     bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4519                 } else {  // Reset the font to default for normal bookmarks.
4520                     bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4521                 }
4522             }
4523         };
4524
4525         // Get a handle for the bookmarks list view.
4526         ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
4527
4528         // Populate the list view with the adapter.
4529         bookmarksListView.setAdapter(bookmarksCursorAdapter);
4530
4531         // Get a handle for the bookmarks title text view.
4532         TextView bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
4533
4534         // Set the bookmarks drawer title.
4535         if (currentBookmarksFolder.isEmpty()) {
4536             bookmarksTitleTextView.setText(R.string.bookmarks);
4537         } else {
4538             bookmarksTitleTextView.setText(currentBookmarksFolder);
4539         }
4540     }
4541
4542     private void openWithApp(String url) {
4543         // Create an open with app intent with `ACTION_VIEW`.
4544         Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
4545
4546         // Set the URI but not the MIME type.  This should open all available apps.
4547         openWithAppIntent.setData(Uri.parse(url));
4548
4549         // Flag the intent to open in a new task.
4550         openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4551
4552         // Try the intent.
4553         try {
4554             // Show the chooser.
4555             startActivity(openWithAppIntent);
4556         } catch (ActivityNotFoundException exception) {  // There are no apps available to open the URL.
4557             // Show a snackbar with the error.
4558             Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
4559         }
4560     }
4561
4562     private void openWithBrowser(String url) {
4563         // Create an open with browser intent with `ACTION_VIEW`.
4564         Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
4565
4566         // Set the URI and the MIME type.  `"text/html"` should load browser options.
4567         openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
4568
4569         // Flag the intent to open in a new task.
4570         openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4571
4572         // Try the intent.
4573         try {
4574             // Show the chooser.
4575             startActivity(openWithBrowserIntent);
4576         } catch (ActivityNotFoundException exception) {  // There are no browsers available to open the URL.
4577             // Show a snackbar with the error.
4578             Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
4579         }
4580     }
4581
4582     private String sanitizeUrl(String url) {
4583         // Sanitize tracking queries.
4584         if (sanitizeTrackingQueries)
4585             url = sanitizeUrlHelper.sanitizeTrackingQueries(url);
4586
4587         // Sanitize AMP redirects.
4588         if (sanitizeAmpRedirects)
4589             url = sanitizeUrlHelper.sanitizeAmpRedirects(url);
4590
4591         // Return the sanitized URL.
4592         return url;
4593     }
4594
4595     public void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
4596         // Store the blocklists.
4597         easyList = combinedBlocklists.get(0);
4598         easyPrivacy = combinedBlocklists.get(1);
4599         fanboysAnnoyanceList = combinedBlocklists.get(2);
4600         fanboysSocialList = combinedBlocklists.get(3);
4601         ultraList = combinedBlocklists.get(4);
4602         ultraPrivacy = combinedBlocklists.get(5);
4603
4604         // Check to see if the activity has been restarted with a saved state.
4605         if ((savedStateArrayList == null) || (savedStateArrayList.size() == 0)) {  // The activity has not been restarted or it was restarted on start to force the night theme.
4606             // Add the first tab.
4607             addNewTab("", true);
4608         } else {  // The activity has been restarted.
4609             // Restore each tab.  Once the minimum API >= 24, a `forEach()` command can be used.
4610             for (int i = 0; i < savedStateArrayList.size(); i++) {
4611                 // Add a new tab.
4612                 tabLayout.addTab(tabLayout.newTab());
4613
4614                 // Get the new tab.
4615                 TabLayout.Tab newTab = tabLayout.getTabAt(i);
4616
4617                 // Remove the lint warning below that the current tab might be null.
4618                 assert newTab != null;
4619
4620                 // Set a custom view on the new tab.
4621                 newTab.setCustomView(R.layout.tab_custom_view);
4622
4623                 // Add the new page.
4624                 webViewPagerAdapter.restorePage(savedStateArrayList.get(i), savedNestedScrollWebViewStateArrayList.get(i));
4625             }
4626
4627             // Reset the saved state variables.
4628             savedStateArrayList = null;
4629             savedNestedScrollWebViewStateArrayList = null;
4630
4631             // Restore the selected tab position.
4632             if (savedTabPosition == 0) {  // The first tab is selected.
4633                 // Set the first page as the current WebView.
4634                 setCurrentWebView(0);
4635             } else {  // the first tab is not selected.
4636                 // Move to the selected tab.
4637                 webViewPager.setCurrentItem(savedTabPosition);
4638             }
4639
4640             // Get the intent that started the app.
4641             Intent intent = getIntent();
4642
4643             // Reset the intent.  This prevents a duplicate tab from being created on restart.
4644             setIntent(new Intent());
4645
4646             // Get the information from the intent.
4647             String intentAction = intent.getAction();
4648             Uri intentUriData = intent.getData();
4649             String intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT);
4650
4651             // Determine if this is a web search.
4652             boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
4653
4654             // 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.
4655             if (intentUriData != null || intentStringExtra != null || isWebSearch) {
4656                 // Get the shared preferences.
4657                 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4658
4659                 // Create a URL string.
4660                 String url;
4661
4662                 // If the intent action is a web search, perform the search.
4663                 if (isWebSearch) {  // The intent is a web search.
4664                     // Create an encoded URL string.
4665                     String encodedUrlString;
4666
4667                     // Sanitize the search input and convert it to a search.
4668                     try {
4669                         encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
4670                     } catch (UnsupportedEncodingException exception) {
4671                         encodedUrlString = "";
4672                     }
4673
4674                     // Add the base search URL.
4675                     url = searchURL + encodedUrlString;
4676                 } else if (intentUriData != null) {  // The intent contains a URL formatted as a URI.
4677                     // Set the intent data as the URL.
4678                     url = intentUriData.toString();
4679                 } else {  // The intent contains a string, which might be a URL.
4680                     // Set the intent string as the URL.
4681                     url = intentStringExtra;
4682                 }
4683
4684                 // Add a new tab if specified in the preferences.
4685                 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) {  // Load the URL in a new tab.
4686                     // Set the loading new intent flag.
4687                     loadingNewIntent = true;
4688
4689                     // Add a new tab.
4690                     addNewTab(url, true);
4691                 } else {  // Load the URL in the current tab.
4692                     // Make it so.
4693                     loadUrl(currentWebView, url);
4694                 }
4695             }
4696         }
4697     }
4698
4699     public void addTab(View view) {
4700         // Add a new tab with a blank URL.
4701         addNewTab("", true);
4702     }
4703
4704     private void addNewTab(String url, boolean moveToTab) {
4705         // Clear the focus from the URL edit text, so that it will be populated with the information from the new tab.
4706         urlEditText.clearFocus();
4707
4708         // Get the new page number.  The page numbers are 0 indexed, so the new page number will match the current count.
4709         int newTabNumber = tabLayout.getTabCount();
4710
4711         // Add a new tab.
4712         tabLayout.addTab(tabLayout.newTab());
4713
4714         // Get the new tab.
4715         TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber);
4716
4717         // Remove the lint warning below that the current tab might be null.
4718         assert newTab != null;
4719
4720         // Set a custom view on the new tab.
4721         newTab.setCustomView(R.layout.tab_custom_view);
4722
4723         // Add the new WebView page.
4724         webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab);
4725
4726         // Show the app bar if it is at the bottom of the screen and the new tab is taking focus.
4727         if (bottomAppBar && moveToTab && (appBarLayout.getTranslationY() != 0)) {
4728             // Animate the bottom app bar onto the screen.
4729             objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
4730
4731             // Make it so.
4732             objectAnimator.start();
4733         }
4734     }
4735
4736     public void closeTab(View view) {
4737         // Run the command according to the number of tabs.
4738         if (tabLayout.getTabCount() > 1) {  // There is more than one tab open.
4739             // Close the current tab.
4740             closeCurrentTab();
4741         } else {  // There is only one tab open.
4742             clearAndExit();
4743         }
4744     }
4745
4746     private void closeCurrentTab() {
4747         // Get the current tab number.
4748         int currentTabNumber = tabLayout.getSelectedTabPosition();
4749
4750         // Delete the current tab.
4751         tabLayout.removeTabAt(currentTabNumber);
4752
4753         // Delete the current page.  If the selected page number did not change during the delete (because the newly selected tab has has same number as the previously deleted tab), it will return true,
4754         // meaning that the current WebView must be reset.  Otherwise it will happen automatically as the selected tab number changes.
4755         if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
4756             setCurrentWebView(currentTabNumber);
4757         }
4758     }
4759
4760     private void exitFullScreenVideo() {
4761         // Re-enable the screen timeout.
4762         fullScreenVideoFrameLayout.setKeepScreenOn(false);
4763
4764         // Unset the full screen video flag.
4765         displayingFullScreenVideo = false;
4766
4767         // Remove all the views from the full screen video frame layout.
4768         fullScreenVideoFrameLayout.removeAllViews();
4769
4770         // Hide the full screen video frame layout.
4771         fullScreenVideoFrameLayout.setVisibility(View.GONE);
4772
4773         // Enable the sliding drawers.
4774         drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
4775
4776         // Show the coordinator layout.
4777         coordinatorLayout.setVisibility(View.VISIBLE);
4778
4779         // Apply the appropriate full screen mode flags.
4780         if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) {  // Privacy Browser is currently in full screen browsing mode.
4781             // Hide the app bar if specified.
4782             if (hideAppBar) {
4783                 // Hide the tab linear layout.
4784                 tabsLinearLayout.setVisibility(View.GONE);
4785
4786                 // Hide the action bar.
4787                 actionBar.hide();
4788             }
4789
4790             /* Hide the system bars.
4791              * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4792              * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4793              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4794              * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4795              */
4796             rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
4797                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4798         } else {  // Switch to normal viewing mode.
4799             // Remove the `SYSTEM_UI` flags from the root frame layout.
4800             rootFrameLayout.setSystemUiVisibility(0);
4801         }
4802     }
4803
4804     private void clearAndExit() {
4805         // Get a handle for the shared preferences.
4806         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4807
4808         // Close the bookmarks cursor and database.
4809         bookmarksCursor.close();
4810         bookmarksDatabaseHelper.close();
4811
4812         // Get the status of the clear everything preference.
4813         boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
4814
4815         // Get a handle for the runtime.
4816         Runtime runtime = Runtime.getRuntime();
4817
4818         // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
4819         // which links to `/data/data/com.stoutner.privacybrowser.standard`.
4820         String privateDataDirectoryString = getApplicationInfo().dataDir;
4821
4822         // Clear cookies.
4823         if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
4824             // Request the cookies be deleted.
4825             CookieManager.getInstance().removeAllCookies(null);
4826
4827             // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4828             try {
4829                 // Two commands must be used because `Runtime.exec()` does not like `*`.
4830                 Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
4831                 Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
4832
4833                 // Wait until the processes have finished.
4834                 deleteCookiesProcess.waitFor();
4835                 deleteCookiesJournalProcess.waitFor();
4836             } catch (Exception exception) {
4837                 // Do nothing if an error is thrown.
4838             }
4839         }
4840
4841         // Clear DOM storage.
4842         if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
4843             // Ask `WebStorage` to clear the DOM storage.
4844             WebStorage webStorage = WebStorage.getInstance();
4845             webStorage.deleteAllData();
4846
4847             // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4848             try {
4849                 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4850                 Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
4851
4852                 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
4853                 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
4854                 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
4855                 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
4856                 Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
4857
4858                 // Wait until the processes have finished.
4859                 deleteLocalStorageProcess.waitFor();
4860                 deleteIndexProcess.waitFor();
4861                 deleteQuotaManagerProcess.waitFor();
4862                 deleteQuotaManagerJournalProcess.waitFor();
4863                 deleteDatabaseProcess.waitFor();
4864             } catch (Exception exception) {
4865                 // Do nothing if an error is thrown.
4866             }
4867         }
4868
4869         // Clear form data if the API < 26.
4870         if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
4871             WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
4872             webViewDatabase.clearFormData();
4873
4874             // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4875             try {
4876                 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
4877                 Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
4878                 Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
4879
4880                 // Wait until the processes have finished.
4881                 deleteWebDataProcess.waitFor();
4882                 deleteWebDataJournalProcess.waitFor();
4883             } catch (Exception exception) {
4884                 // Do nothing if an error is thrown.
4885             }
4886         }
4887
4888         // Clear the logcat.
4889         if (clearEverything || sharedPreferences.getBoolean(getString(R.string.clear_logcat_key), true)) {
4890             try {
4891                 // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
4892                 Process process = Runtime.getRuntime().exec("logcat -b all -c");
4893
4894                 // Wait for the process to finish.
4895                 process.waitFor();
4896             } catch (IOException|InterruptedException exception) {
4897                 // Do nothing.
4898             }
4899         }
4900
4901         // Clear the cache.
4902         if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
4903             // Clear the cache from each WebView.
4904             for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4905                 // Get the WebView tab fragment.
4906                 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4907
4908                 // Get the WebView fragment view.
4909                 View webViewFragmentView = webViewTabFragment.getView();
4910
4911                 // Only clear the cache if the WebView exists.
4912                 if (webViewFragmentView != null) {
4913                     // Get the nested scroll WebView from the tab fragment.
4914                     NestedScrollWebView nestedScrollWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview);
4915
4916                     // Clear the cache for this WebView.
4917                     nestedScrollWebView.clearCache(true);
4918                 }
4919             }
4920
4921             // Manually delete the cache directories.
4922             try {
4923                 // Delete the main cache directory.
4924                 Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache");
4925
4926                 // Delete the secondary `Service Worker` cache directory.
4927                 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4928                 Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Default/Service Worker/"});
4929
4930                 // Wait until the processes have finished.
4931                 deleteCacheProcess.waitFor();
4932                 deleteServiceWorkerProcess.waitFor();
4933             } catch (Exception exception) {
4934                 // Do nothing if an error is thrown.
4935             }
4936         }
4937
4938         // Wipe out each WebView.
4939         for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4940             // Get the WebView tab fragment.
4941             WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4942
4943             // Get the WebView frame layout.
4944             FrameLayout webViewFrameLayout = (FrameLayout) webViewTabFragment.getView();
4945
4946             // Only wipe out the WebView if it exists.
4947             if (webViewFrameLayout != null) {
4948                 // Get the nested scroll WebView from the tab fragment.
4949                 NestedScrollWebView nestedScrollWebView = webViewFrameLayout.findViewById(R.id.nestedscroll_webview);
4950
4951                 // Clear SSL certificate preferences for this WebView.
4952                 nestedScrollWebView.clearSslPreferences();
4953
4954                 // Clear the back/forward history for this WebView.
4955                 nestedScrollWebView.clearHistory();
4956
4957                 // Remove all the views from the frame layout.
4958                 webViewFrameLayout.removeAllViews();
4959
4960                 // Destroy the internal state of the WebView.
4961                 nestedScrollWebView.destroy();
4962             }
4963         }
4964
4965         // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
4966         // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
4967         if (clearEverything) {
4968             try {
4969                 // Delete the folder.
4970                 Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
4971
4972                 // Wait until the process has finished.
4973                 deleteAppWebviewProcess.waitFor();
4974             } catch (Exception exception) {
4975                 // Do nothing if an error is thrown.
4976             }
4977         }
4978
4979         // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
4980         finishAndRemoveTask();
4981
4982         // Remove the terminated program from RAM.  The status code is `0`.
4983         System.exit(0);
4984     }
4985
4986     public void bookmarksBack(View view) {
4987         if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
4988             // close the bookmarks drawer.
4989             drawerLayout.closeDrawer(GravityCompat.END);
4990         } else {  // A subfolder is displayed.
4991             // Place the former parent folder in `currentFolder`.
4992             currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
4993
4994             // Load the new folder.
4995             loadBookmarksFolder();
4996         }
4997     }
4998
4999     private void setCurrentWebView(int pageNumber) {
5000         // Stop the swipe to refresh indicator if it is running
5001         swipeRefreshLayout.setRefreshing(false);
5002
5003         // Get the WebView tab fragment.
5004         WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(pageNumber);
5005
5006         // Get the fragment view.
5007         View webViewFragmentView = webViewTabFragment.getView();
5008
5009         // Set the current WebView if the fragment view is not null.
5010         if (webViewFragmentView != null) {  // The fragment has been populated.
5011             // Store the current WebView.
5012             currentWebView = webViewFragmentView.findViewById(R.id.nestedscroll_webview);
5013
5014             // Update the status of swipe to refresh.
5015             if (currentWebView.getSwipeToRefresh()) {  // Swipe to refresh is enabled.
5016                 // Enable the swipe refresh layout if the WebView is scrolled all the way to the top.  It is updated every time the scroll changes.
5017                 swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
5018             } else {  // Swipe to refresh is disabled.
5019                 // Disable the swipe refresh layout.
5020                 swipeRefreshLayout.setEnabled(false);
5021             }
5022
5023             // Get a handle for the cookie manager.
5024             CookieManager cookieManager = CookieManager.getInstance();
5025
5026             // Set the cookie status.
5027             cookieManager.setAcceptCookie(currentWebView.getAcceptCookies());
5028
5029             // Update the privacy icons.  `true` redraws the icons in the app bar.
5030             updatePrivacyIcons(true);
5031
5032             // Get a handle for the input method manager.
5033             InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
5034
5035             // Remove the lint warning below that the input method manager might be null.
5036             assert inputMethodManager != null;
5037
5038             // Get the current URL.
5039             String url = currentWebView.getUrl();
5040
5041             // Update the URL edit text if not loading a new intent.  Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
5042             if (!loadingNewIntent) {  // A new intent is not being loaded.
5043                 if ((url == null) || url.equals("about:blank")) {  // The WebView is blank.
5044                     // Display the hint in the URL edit text.
5045                     urlEditText.setText("");
5046
5047                     // Request focus for the URL text box.
5048                     urlEditText.requestFocus();
5049
5050                     // Display the keyboard.
5051                     inputMethodManager.showSoftInput(urlEditText, 0);
5052                 } else {  // The WebView has a loaded URL.
5053                     // Clear the focus from the URL text box.
5054                     urlEditText.clearFocus();
5055
5056                     // Hide the soft keyboard.
5057                     inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
5058
5059                     // Display the current URL in the URL text box.
5060                     urlEditText.setText(url);
5061
5062                     // Highlight the URL text.
5063                     highlightUrlText();
5064                 }
5065             } else {  // A new intent is being loaded.
5066                 // Reset the loading new intent tracker.
5067                 loadingNewIntent = false;
5068             }
5069
5070             // Set the background to indicate the domain settings status.
5071             if (currentWebView.getDomainSettingsApplied()) {
5072                 // Set a background on the URL relative layout to indicate that custom domain settings are being used.
5073                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.domain_settings_url_background, null));
5074             } else {
5075                 // Remove any background on the URL relative layout.
5076                 urlRelativeLayout.setBackground(ResourcesCompat.getDrawable(getResources(), R.color.transparent, null));
5077             }
5078         } else {  // The fragment has not been populated.  Try again in 100 milliseconds.
5079             // Create a handler to set the current WebView.
5080             Handler setCurrentWebViewHandler = new Handler();
5081
5082             // Create a runnable to set the current WebView.
5083             Runnable setCurrentWebWebRunnable = () -> {
5084                 // Set the current WebView.
5085                 setCurrentWebView(pageNumber);
5086             };
5087
5088             // Try setting the current WebView again after 100 milliseconds.
5089             setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100);
5090         }
5091     }
5092
5093     @SuppressLint("ClickableViewAccessibility")
5094     @Override
5095     public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url, Boolean restoringState) {
5096         // Get a handle for the shared preferences.
5097         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
5098
5099         // Get the WebView theme.
5100         String webViewTheme = sharedPreferences.getString("webview_theme", getString(R.string.webview_theme_default_value));
5101
5102         // Get the WebView theme entry values string array.
5103         String[] webViewThemeEntryValuesStringArray = getResources().getStringArray(R.array.webview_theme_entry_values);
5104
5105         // Apply the WebView theme if supported by the installed WebView.
5106         if ((Build.VERSION.SDK_INT >= 33) && WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {  // The device is running API >= 33 and algorithmic darkening is supported.
5107             // Set the WebView them.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
5108             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
5109                 // Turn off algorithmic darkening.
5110                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
5111
5112                 // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
5113                 // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
5114                 nestedScrollWebView.setVisibility(View.VISIBLE);
5115             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
5116                 // Turn on algorithmic darkening.
5117                 WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
5118             } else {
5119                 // The system default theme is selected.
5120                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
5121
5122                 // Set the algorithmic darkening according to the current system theme status.
5123                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
5124                     // Turn off algorithmic darkening.
5125                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), false);
5126
5127                     // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
5128                     // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
5129                     nestedScrollWebView.setVisibility(View.VISIBLE);
5130                 } else {  // The system is in night mode.
5131                     // Turn on algorithmic darkening.
5132                     WebSettingsCompat.setAlgorithmicDarkeningAllowed(nestedScrollWebView.getSettings(), true);
5133                 }
5134             }
5135         } else if ((Build.VERSION.SDK_INT < 33) && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The device is running API < 33 and the WebView supports force dark.
5136             // Set the WebView theme.  A switch statement cannot be used because the WebView theme entry values string array is not a compile time constant.
5137             if (webViewTheme.equals(webViewThemeEntryValuesStringArray[1])) {  // The light theme is selected.
5138                 // Turn off the WebView dark mode.
5139                 //noinspection deprecation
5140                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
5141
5142                 // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
5143                 // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
5144                 nestedScrollWebView.setVisibility(View.VISIBLE);
5145             } else if (webViewTheme.equals(webViewThemeEntryValuesStringArray[2])) {  // The dark theme is selected.
5146                 // Turn on the WebView dark mode.
5147                 //noinspection deprecation
5148                 WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
5149             } else {  // The system default theme is selected.
5150                 // Get the current system theme status.
5151                 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
5152
5153                 // Set the WebView theme according to the current system theme status.
5154                 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {  // The system is in day mode.
5155                     // Turn off the WebView dark mode.
5156                     //noinspection deprecation
5157                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
5158
5159                     // Make the WebView visible. The WebView was created invisible in `webview_framelayout` to prevent a white background splash in night mode.
5160                     // If the system is currently in night mode, showing the WebView will be handled in `onProgressChanged()`.
5161                     nestedScrollWebView.setVisibility(View.VISIBLE);
5162                 } else {  // The system is in night mode.
5163                     // Turn on the WebView dark mode.
5164                     //noinspection deprecation
5165                     WebSettingsCompat.setForceDark(nestedScrollWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
5166                 }
5167             }
5168         }
5169
5170         // Get a handle for the activity
5171         Activity activity = this;
5172
5173         // Get a handle for the input method manager.
5174         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
5175
5176         // Instantiate the blocklist helper.
5177         BlocklistHelper blocklistHelper = new BlocklistHelper();
5178
5179         // Remove the lint warning below that the input method manager might be null.
5180         assert inputMethodManager != null;
5181
5182         // Set the app bar scrolling.
5183         nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
5184
5185         // Allow pinch to zoom.
5186         nestedScrollWebView.getSettings().setBuiltInZoomControls(true);
5187
5188         // Hide zoom controls.
5189         nestedScrollWebView.getSettings().setDisplayZoomControls(false);
5190
5191         // Don't allow mixed content (HTTP and HTTPS) on the same website.
5192         nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
5193
5194         // Set the WebView to load in overview mode (zoomed out to the maximum width).
5195         nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
5196
5197         // Explicitly disable geolocation.
5198         nestedScrollWebView.getSettings().setGeolocationEnabled(false);
5199
5200         // Allow loading of file:// URLs.  This is necessary for opening MHT web archives, which are copies into a temporary cache location.
5201         nestedScrollWebView.getSettings().setAllowFileAccess(true);
5202
5203         // Create a double-tap gesture detector to toggle full-screen mode.
5204         GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
5205             // Override `onDoubleTap()`.  All other events are handled using the default settings.
5206             @Override
5207             public boolean onDoubleTap(MotionEvent event) {
5208                 if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
5209                     // Toggle the full screen browsing mode tracker.
5210                     inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
5211
5212                     // Toggle the full screen browsing mode.
5213                     if (inFullScreenBrowsingMode) {  // Switch to full screen mode.
5214                         // Hide the app bar if specified.
5215                         if (hideAppBar) {  // The app bar is hidden.
5216                             // Close the find on page bar if it is visible.
5217                             closeFindOnPage(null);
5218
5219                             // Hide the tab linear layout.
5220                             tabsLinearLayout.setVisibility(View.GONE);
5221
5222                             // Hide the action bar.
5223                             actionBar.hide();
5224
5225                             // Set layout and scrolling parameters according to the position of the app bar.
5226                             if (bottomAppBar) {  // The app bar is at the bottom.
5227                                 // Reset the WebView padding to fill the available space.
5228                                 swipeRefreshLayout.setPadding(0, 0, 0, 0);
5229                             } else {  // The app bar is at the top.
5230                                 // Check to see if the app bar is normally scrolled.
5231                                 if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
5232                                     // Get the swipe refresh layout parameters.
5233                                     CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
5234
5235                                     // Remove the off-screen scrolling layout.
5236                                     swipeRefreshLayoutParams.setBehavior(null);
5237                                 } else {  // The app bar is not scrolled when it is displayed.
5238                                     // Remove the padding from the top of the swipe refresh layout.
5239                                     swipeRefreshLayout.setPadding(0, 0, 0, 0);
5240
5241                                     // The swipe refresh circle must be moved above the now removed status bar location.
5242                                     swipeRefreshLayout.setProgressViewOffset(false, -200, defaultProgressViewEndOffset);
5243                                 }
5244                             }
5245                         } else {  // The app bar is not hidden.
5246                             // Adjust the UI for the bottom app bar.
5247                             if (bottomAppBar) {
5248                                 // Adjust the UI according to the scrolling of the app bar.
5249                                 if (scrollAppBar) {
5250                                     // Reset the WebView padding to fill the available space.
5251                                     swipeRefreshLayout.setPadding(0, 0, 0, 0);
5252                                 } else {
5253                                     // Move the WebView above the app bar layout.
5254                                     swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
5255                                 }
5256                             }
5257                         }
5258
5259                         /* Hide the system bars.
5260                          * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5261                          * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5262                          * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5263                          * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5264                          */
5265                         rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5266                                 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5267                     } else {  // Switch to normal viewing mode.
5268                         // Show the app bar if it was hidden.
5269                         if (hideAppBar) {
5270                             // Show the tab linear layout.
5271                             tabsLinearLayout.setVisibility(View.VISIBLE);
5272
5273                             // Show the action bar.
5274                             actionBar.show();
5275                         }
5276
5277                         // Set layout and scrolling parameters according to the position of the app bar.
5278                         if (bottomAppBar) {  // The app bar is at the bottom.
5279                             // Adjust the UI.
5280                             if (scrollAppBar) {
5281                                 // Reset the WebView padding to fill the available space.
5282                                 swipeRefreshLayout.setPadding(0, 0, 0, 0);
5283                             } else {
5284                                 // Move the WebView above the app bar layout.
5285                                 swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
5286                             }
5287                         } else {  // The app bar is at the top.
5288                             // Check to see if the app bar is normally scrolled.
5289                             if (scrollAppBar) {  // The app bar is scrolled when it is displayed.
5290                                 // Get the swipe refresh layout parameters.
5291                                 CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
5292
5293                                 // Add the off-screen scrolling layout.
5294                                 swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
5295                             } else {  // The app bar is not scrolled when it is displayed.
5296                                 // The swipe refresh layout must be manually moved below the app bar layout.
5297                                 swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
5298
5299                                 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5300                                 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
5301                             }
5302                         }
5303
5304                         // Remove the `SYSTEM_UI` flags from the root frame layout.
5305                         rootFrameLayout.setSystemUiVisibility(0);
5306                     }
5307
5308                     // Consume the double-tap.
5309                     return true;
5310                 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
5311                     return false;
5312                 }
5313             }
5314
5315             @Override
5316             public boolean onFling(MotionEvent motionEvent1, MotionEvent motionEvent2, float velocityX, float velocityY) {
5317                 // Scroll the bottom app bar if enabled.
5318                 if (bottomAppBar && scrollAppBar && !objectAnimator.isRunning()) {
5319                     // Calculate the Y change.
5320                     float motionY = motionEvent2.getY() - motionEvent1.getY();
5321
5322                     // Scroll the app bar if the change is greater than 50 pixels.
5323                     if (motionY > 50) {
5324                         // Animate the bottom app bar onto the screen.
5325                         objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", 0);
5326                     } else if (motionY < -50) {
5327                         // Animate the bottom app bar off the screen.
5328                         objectAnimator = ObjectAnimator.ofFloat(appBarLayout, "translationY", appBarLayout.getHeight());
5329                     }
5330
5331                     // Make it so.
5332                     objectAnimator.start();
5333                 }
5334
5335                 // Do not consume the event.
5336                 return false;
5337             }
5338         });
5339
5340         // Pass all touch events on the WebView through the double-tap gesture detector.
5341         nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> {
5342             // Call `performClick()` on the view, which is required for accessibility.
5343             view.performClick();
5344
5345             // Send the event to the gesture detector.
5346             return doubleTapGestureDetector.onTouchEvent(event);
5347         });
5348
5349         // Register the WebView for a context menu.  This is used to see link targets and download images.
5350         registerForContextMenu(nestedScrollWebView);
5351
5352         // Allow the downloading of files.
5353         nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
5354             // Check the download preference.
5355             if (downloadWithExternalApp) {  // Download with an external app.
5356                 downloadUrlWithExternalApp(downloadUrl);
5357             } else {  // Handle the download inside of Privacy Browser.
5358                 // Define a formatted file size string.
5359                 String formattedFileSizeString;
5360
5361                 // Process the content length if it contains data.
5362                 if (contentLength > 0) {  // The content length is greater than 0.
5363                     // Format the content length as a string.
5364                     formattedFileSizeString = NumberFormat.getInstance().format(contentLength) + " " + getString(R.string.bytes);
5365                 } else {  // The content length is not greater than 0.
5366                     // Set the formatted file size string to be `unknown size`.
5367                     formattedFileSizeString = getString(R.string.unknown_size);
5368                 }
5369
5370                 // Get the file name from the content disposition.
5371                 String fileNameString = PrepareSaveDialog.getFileNameFromHeaders(this, contentDisposition, mimetype, downloadUrl);
5372
5373                 // Instantiate the save dialog.
5374                 DialogFragment saveDialogFragment = SaveDialog.saveUrl(downloadUrl, formattedFileSizeString, fileNameString, userAgent,
5375                         nestedScrollWebView.getAcceptCookies());
5376
5377                 // Try to show the dialog.  The download listener continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
5378                 try {
5379                     // Show the save dialog.  It must be named `save_dialog` so that the file picker can update the file name.
5380                     saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
5381                 } catch (Exception exception) {  // The dialog could not be shown.
5382                     // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
5383                     pendingDialogsArrayList.add(new PendingDialog(saveDialogFragment, getString(R.string.save_dialog)));
5384                 }
5385             }
5386         });
5387
5388         // Update the find on page count.
5389         nestedScrollWebView.setFindListener(new WebView.FindListener() {
5390             // Get a handle for `findOnPageCountTextView`.
5391             final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
5392
5393             @Override
5394             public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
5395                 if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
5396                     // Set `findOnPageCountTextView` to `0/0`.
5397                     findOnPageCountTextView.setText(R.string.zero_of_zero);
5398                 } else if (isDoneCounting) {  // There are matches.
5399                     // `activeMatchOrdinal` is zero-based.
5400                     int activeMatch = activeMatchOrdinal + 1;
5401
5402                     // Build the match string.
5403                     String matchString = activeMatch + "/" + numberOfMatches;
5404
5405                     // Set `findOnPageCountTextView`.
5406                     findOnPageCountTextView.setText(matchString);
5407                 }
5408             }
5409         });
5410
5411         // Process scroll changes.
5412         nestedScrollWebView.setOnScrollChangeListener((view, scrollX, scrollY, oldScrollX, oldScrollY) -> {
5413             // Set the swipe to refresh status.
5414             if (nestedScrollWebView.getSwipeToRefresh()) {
5415                 // Only enable swipe to refresh if the WebView is scrolled to the top.
5416                 swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
5417             } else {
5418                 // Disable swipe to refresh.
5419                 swipeRefreshLayout.setEnabled(false);
5420             }
5421
5422             // Reinforce the system UI visibility flags if in full screen browsing mode.
5423             // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
5424             if (inFullScreenBrowsingMode) {
5425                 /* Hide the system bars.
5426                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5427                  * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5428                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5429                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5430                  */
5431                 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5432                         View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5433             }
5434         });
5435
5436         // Set the web chrome client.
5437         nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
5438             // Update the progress bar when a page is loading.
5439             @Override
5440             public void onProgressChanged(WebView view, int progress) {
5441                 // Update the progress bar.
5442                 progressBar.setProgress(progress);
5443
5444                 // Set the visibility of the progress bar.
5445                 if (progress < 100) {
5446                     // Show the progress bar.
5447                     progressBar.setVisibility(View.VISIBLE);
5448                 } else {
5449                     // Hide the progress bar.
5450                     progressBar.setVisibility(View.GONE);
5451
5452                     //Stop the swipe to refresh indicator if it is running
5453                     swipeRefreshLayout.setRefreshing(false);
5454
5455                     // Make the current WebView visible.  If this is a new tab, the current WebView would have been created invisible in `webview_framelayout` to prevent a white background splash in night mode.
5456                     nestedScrollWebView.setVisibility(View.VISIBLE);
5457                 }
5458             }
5459
5460             // Set the favorite icon when it changes.
5461             @Override
5462             public void onReceivedIcon(WebView view, Bitmap icon) {
5463                 // Only update the favorite icon if the website has finished loading.
5464                 if (progressBar.getVisibility() == View.GONE) {
5465                     // Store the new favorite icon.
5466                     nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
5467
5468                     // Get the current page position.
5469                     int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5470
5471                     // Get the current tab.
5472                     TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
5473
5474                     // Check to see if the tab has been populated.
5475                     if (tab != null) {
5476                         // Get the custom view from the tab.
5477                         View tabView = tab.getCustomView();
5478
5479                         // Check to see if the custom tab view has been populated.
5480                         if (tabView != null) {
5481                             // Get the favorite icon image view from the tab.
5482                             ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview);
5483
5484                             // Display the favorite icon in the tab.
5485                             tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
5486                         }
5487                     }
5488                 }
5489             }
5490
5491             // Save a copy of the title when it changes.
5492             @Override
5493             public void onReceivedTitle(WebView view, String title) {
5494                 // Get the current page position.
5495                 int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5496
5497                 // Get the current tab.
5498                 TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
5499
5500                 // Only populate the title text view if the tab has been fully created.
5501                 if (tab != null) {
5502                     // Get the custom view from the tab.
5503                     View tabView = tab.getCustomView();
5504
5505                     // Only populate the title text view if the tab view has been fully populated.
5506                     if (tabView != null) {
5507                         // Get the title text view from the tab.
5508                         TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
5509
5510                         // Set the title according to the URL.
5511                         if (title.equals("about:blank")) {
5512                             // Set the title to indicate a new tab.
5513                             tabTitleTextView.setText(R.string.new_tab);
5514                         } else {
5515                             // Set the title as the tab text.
5516                             tabTitleTextView.setText(title);
5517                         }
5518                     }
5519                 }
5520             }
5521
5522             // Enter full screen video.
5523             @Override
5524             public void onShowCustomView(View video, CustomViewCallback callback) {
5525                 // Set the full screen video flag.
5526                 displayingFullScreenVideo = true;
5527
5528                 // Hide the keyboard.
5529                 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
5530
5531                 // Hide the coordinator layout.
5532                 coordinatorLayout.setVisibility(View.GONE);
5533
5534                 /* Hide the system bars.
5535                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5536                  * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5537                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5538                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5539                  */
5540                 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5541                         View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5542
5543                 // Disable the sliding drawers.
5544                 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
5545
5546                 // Add the video view to the full screen video frame layout.
5547                 fullScreenVideoFrameLayout.addView(video);
5548
5549                 // Show the full screen video frame layout.
5550                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
5551
5552                 // Disable the screen timeout while the video is playing.  YouTube does this automatically, but not all other videos do.
5553                 fullScreenVideoFrameLayout.setKeepScreenOn(true);
5554             }
5555
5556             // Exit full screen video.
5557             @Override
5558             public void onHideCustomView() {
5559                 // Exit the full screen video.
5560                 exitFullScreenVideo();
5561             }
5562
5563             // Upload files.
5564             @Override
5565             public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
5566                 // Store the file path callback.
5567                 fileChooserCallback = filePathCallback;
5568
5569                 // Create an intent to open a chooser based on the file chooser parameters.
5570                 Intent fileChooserIntent = fileChooserParams.createIntent();
5571
5572                 // Get a handle for the package manager.
5573                 PackageManager packageManager = getPackageManager();
5574
5575                 // Check to see if the file chooser intent resolves to an installed package.
5576                 if (fileChooserIntent.resolveActivity(packageManager) != null) {  // The file chooser intent is fine.
5577                     // Start the file chooser intent.
5578                     startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
5579                 } else {  // The file chooser intent will cause a crash.
5580                     // Create a generic intent to open a chooser.
5581                     Intent genericFileChooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
5582
5583                     // Request an openable file.
5584                     genericFileChooserIntent.addCategory(Intent.CATEGORY_OPENABLE);
5585
5586                     // Set the file type to everything.
5587                     genericFileChooserIntent.setType("*/*");
5588
5589                     // Start the generic file chooser intent.
5590                     startActivityForResult(genericFileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
5591                 }
5592                 return true;
5593             }
5594         });
5595
5596         nestedScrollWebView.setWebViewClient(new WebViewClient() {
5597             // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
5598             // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
5599             @Override
5600             public boolean shouldOverrideUrlLoading(WebView view, String url) {
5601                 // Sanitize the url.
5602                 url = sanitizeUrl(url);
5603
5604                 // Handle the URL according to the type.
5605                 if (url.startsWith("http")) {  // Load the URL in Privacy Browser.
5606                     // Load the URL.  By using `loadUrl()`, instead of `loadUrlFromBase()`, the Referer header will never be sent.
5607                     loadUrl(nestedScrollWebView, url);
5608
5609                     // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
5610                     // Custom headers cannot be added if false is returned and the WebView handles the loading of the URL.
5611                     return true;
5612                 } else if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
5613                     // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
5614                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
5615
5616                     // Parse the url and set it as the data for the intent.
5617                     emailIntent.setData(Uri.parse(url));
5618
5619                     // Open the email program in a new task instead of as part of Privacy Browser.
5620                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5621
5622                     try {
5623                         // Make it so.
5624                         startActivity(emailIntent);
5625                     } catch (ActivityNotFoundException exception) {
5626                         // Display a snackbar.
5627                         Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
5628                     }
5629
5630
5631                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5632                     return true;
5633                 } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
5634                     // Open the dialer and load the phone number, but wait for the user to place the call.
5635                     Intent dialIntent = new Intent(Intent.ACTION_DIAL);
5636
5637                     // Add the phone number to the intent.
5638                     dialIntent.setData(Uri.parse(url));
5639
5640                     // Open the dialer in a new task instead of as part of Privacy Browser.
5641                     dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5642
5643                     try {
5644                         // Make it so.
5645                         startActivity(dialIntent);
5646                     } catch (ActivityNotFoundException exception) {
5647                         // Display a snackbar.
5648                         Snackbar.make(currentWebView, getString(R.string.error) + "  " + exception, Snackbar.LENGTH_INDEFINITE).show();
5649                     }
5650
5651                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5652                     return true;
5653                 } else {  // Load a system chooser to select an app that can handle the URL.
5654                     // Open an app that can handle the URL.
5655                     Intent genericIntent = new Intent(Intent.ACTION_VIEW);
5656
5657                     // Add the URL to the intent.
5658                     genericIntent.setData(Uri.parse(url));
5659
5660                     // List all apps that can handle the URL instead of just opening the first one.
5661                     genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
5662
5663                     // Open the app in a new task instead of as part of Privacy Browser.
5664                     genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5665
5666                     // Start the app or display a snackbar if no app is available to handle the URL.
5667                     try {
5668                         startActivity(genericIntent);
5669                     } catch (ActivityNotFoundException exception) {
5670                         Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + "  " + url, Snackbar.LENGTH_SHORT).show();
5671                     }
5672
5673                     // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5674                     return true;
5675                 }
5676             }
5677
5678             // Check requests against the block lists.  The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
5679             @Override
5680             public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest webResourceRequest) {
5681                 // Get the URL.
5682                 String url = webResourceRequest.getUrl().toString();
5683
5684                 // Check to see if the resource request is for the main URL.
5685                 if (url.equals(nestedScrollWebView.getCurrentUrl())) {
5686                     // `return null` loads the resource request, which should never be blocked if it is the main URL.
5687                     return null;
5688                 }
5689
5690                 // Wait until the blocklists have been populated.  When Privacy Browser is being resumed after having the process killed in the background it will try to load the URLs immediately.
5691                 while (ultraPrivacy == null) {
5692                     // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown.
5693                     synchronized (this) {
5694                         try {
5695                             // Check to see if the blocklists have been populated after 100 ms.
5696                             wait(100);
5697                         } catch (InterruptedException exception) {
5698                             // Do nothing.
5699                         }
5700                     }
5701                 }
5702
5703                 // Create an empty web resource response to be used if the resource request is blocked.
5704                 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
5705
5706                 // Reset the whitelist results tracker.
5707                 String[] whitelistResultStringArray = null;
5708
5709                 // Initialize the third party request tracker.
5710                 boolean isThirdPartyRequest = false;
5711
5712                 // Get the current URL.  `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
5713                 String currentBaseDomain = nestedScrollWebView.getCurrentDomainName();
5714
5715                 // Store a copy of the current domain for use in later requests.
5716                 String currentDomain = currentBaseDomain;
5717
5718                 // Get the request host name.
5719                 String requestBaseDomain = webResourceRequest.getUrl().getHost();
5720
5721                 // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
5722                 if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
5723                     // Determine the current base domain.
5724                     while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
5725                         // Remove the first subdomain.
5726                         currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
5727                     }
5728
5729                     // Determine the request base domain.
5730                     while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) {  // There is at least one subdomain.
5731                         // Remove the first subdomain.
5732                         requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
5733                     }
5734
5735                     // Update the third party request tracker.
5736                     isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
5737                 }
5738
5739                 // Get the current WebView page position.
5740                 int webViewPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5741
5742                 // Determine if the WebView is currently displayed.
5743                 boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
5744
5745                 // Block third-party requests if enabled.
5746                 if (isThirdPartyRequest && nestedScrollWebView.getBlockAllThirdPartyRequests()) {
5747                     // Add the result to the resource requests.
5748                     nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url});
5749
5750                     // Increment the blocked requests counters.
5751                     nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5752                     nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS);
5753
5754                     // Update the titles of the blocklist menu items if the WebView is currently displayed.
5755                     if (webViewDisplayed) {
5756                         // Updating the UI must be run from the UI thread.
5757                         activity.runOnUiThread(() -> {
5758                             // Update the menu item titles.
5759                             navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5760
5761                             // Update the options menu if it has been populated.
5762                             if (optionsMenu != null) {
5763                                 optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5764                                 optionsBlockAllThirdPartyRequestsMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
5765                                         getString(R.string.block_all_third_party_requests));
5766                             }
5767                         });
5768                     }
5769
5770                     // Return an empty web resource response.
5771                     return emptyWebResourceResponse;
5772                 }
5773
5774                 // Check UltraList if it is enabled.
5775                 if (nestedScrollWebView.getUltraListEnabled()) {
5776                     // Check the URL against UltraList.
5777                     String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
5778
5779                     // Process the UltraList results.
5780                     if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraList's blacklist.
5781                         // Add the result to the resource requests.
5782                         nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
5783
5784                         // Increment the blocked requests counters.
5785                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5786                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRALIST);
5787
5788                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5789                         if (webViewDisplayed) {
5790                             // Updating the UI must be run from the UI thread.
5791                             activity.runOnUiThread(() -> {
5792                                 // Update the menu item titles.
5793                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5794
5795                                 // Update the options menu if it has been populated.
5796                                 if (optionsMenu != null) {
5797                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5798                                     optionsUltraListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
5799                                 }
5800                             });
5801                         }
5802
5803                         // The resource request was blocked.  Return an empty web resource response.
5804                         return emptyWebResourceResponse;
5805                     } else if (ultraListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraList's whitelist.
5806                         // Add a whitelist entry to the resource requests array.
5807                         nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
5808
5809                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
5810                         return null;
5811                     }
5812                 }
5813
5814                 // Check UltraPrivacy if it is enabled.
5815                 if (nestedScrollWebView.getUltraPrivacyEnabled()) {
5816                     // Check the URL against UltraPrivacy.
5817                     String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
5818
5819                     // Process the UltraPrivacy results.
5820                     if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched UltraPrivacy's blacklist.
5821                         // Add the result to the resource requests.
5822                         nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5823                                 ultraPrivacyResults[5]});
5824
5825                         // Increment the blocked requests counters.
5826                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5827                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRAPRIVACY);
5828
5829                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5830                         if (webViewDisplayed) {
5831                             // Updating the UI must be run from the UI thread.
5832                             activity.runOnUiThread(() -> {
5833                                 // Update the menu item titles.
5834                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5835
5836                                 // Update the options menu if it has been populated.
5837                                 if (optionsMenu != null) {
5838                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5839                                     optionsUltraPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
5840                                 }
5841                             });
5842                         }
5843
5844                         // The resource request was blocked.  Return an empty web resource response.
5845                         return emptyWebResourceResponse;
5846                     } else if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched UltraPrivacy's whitelist.
5847                         // Add a whitelist entry to the resource requests array.
5848                         nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5849                                 ultraPrivacyResults[5]});
5850
5851                         // The resource request has been allowed by UltraPrivacy.  `return null` loads the requested resource.
5852                         return null;
5853                     }
5854                 }
5855
5856                 // Check EasyList if it is enabled.
5857                 if (nestedScrollWebView.getEasyListEnabled()) {
5858                     // Check the URL against EasyList.
5859                     String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
5860
5861                     // Process the EasyList results.
5862                     if (easyListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyList's blacklist.
5863                         // Add the result to the resource requests.
5864                         nestedScrollWebView.addResourceRequest(new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]});
5865
5866                         // Increment the blocked requests counters.
5867                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5868                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYLIST);
5869
5870                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5871                         if (webViewDisplayed) {
5872                             // Updating the UI must be run from the UI thread.
5873                             activity.runOnUiThread(() -> {
5874                                 // Update the menu item titles.
5875                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5876
5877                                 // Update the options menu if it has been populated.
5878                                 if (optionsMenu != null) {
5879                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5880                                     optionsEasyListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
5881                                 }
5882                             });
5883                         }
5884
5885                         // The resource request was blocked.  Return an empty web resource response.
5886                         return emptyWebResourceResponse;
5887                     } else if (easyListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched EasyList's whitelist.
5888                         // Update the whitelist result string array tracker.
5889                         whitelistResultStringArray = new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]};
5890                     }
5891                 }
5892
5893                 // Check EasyPrivacy if it is enabled.
5894                 if (nestedScrollWebView.getEasyPrivacyEnabled()) {
5895                     // Check the URL against EasyPrivacy.
5896                     String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
5897
5898                     // Process the EasyPrivacy results.
5899                     if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched EasyPrivacy's blacklist.
5900                         // Add the result to the resource requests.
5901                         nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4],
5902                                 easyPrivacyResults[5]});
5903
5904                         // Increment the blocked requests counters.
5905                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5906                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYPRIVACY);
5907
5908                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5909                         if (webViewDisplayed) {
5910                             // Updating the UI must be run from the UI thread.
5911                             activity.runOnUiThread(() -> {
5912                                 // Update the menu item titles.
5913                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5914
5915                                 // Update the options menu if it has been populated.
5916                                 if (optionsMenu != null) {
5917                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5918                                     optionsEasyPrivacyMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
5919                                 }
5920                             });
5921                         }
5922
5923                         // The resource request was blocked.  Return an empty web resource response.
5924                         return emptyWebResourceResponse;
5925                     } else if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched EasyPrivacy's whitelist.
5926                         // Update the whitelist result string array tracker.
5927                         whitelistResultStringArray = new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]};
5928                     }
5929                 }
5930
5931                 // Check Fanboy’s Annoyance List if it is enabled.
5932                 if (nestedScrollWebView.getFanboysAnnoyanceListEnabled()) {
5933                     // Check the URL against Fanboy's Annoyance List.
5934                     String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
5935
5936                     // Process the Fanboy's Annoyance List results.
5937                     if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched Fanboy's Annoyance List's blacklist.
5938                         // Add the result to the resource requests.
5939                         nestedScrollWebView.addResourceRequest(new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5940                                 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]});
5941
5942                         // Increment the blocked requests counters.
5943                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5944                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST);
5945
5946                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5947                         if (webViewDisplayed) {
5948                             // Updating the UI must be run from the UI thread.
5949                             activity.runOnUiThread(() -> {
5950                                 // Update the menu item titles.
5951                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5952
5953                                 // Update the options menu if it has been populated.
5954                                 if (optionsMenu != null) {
5955                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5956                                     optionsFanboysAnnoyanceListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
5957                                             getString(R.string.fanboys_annoyance_list));
5958                                 }
5959                             });
5960                         }
5961
5962                         // The resource request was blocked.  Return an empty web resource response.
5963                         return emptyWebResourceResponse;
5964                     } else if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)){  // The resource request matched Fanboy's Annoyance List's whitelist.
5965                         // Update the whitelist result string array tracker.
5966                         whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5967                                 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
5968                     }
5969                 } else if (nestedScrollWebView.getFanboysSocialBlockingListEnabled()) {  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
5970                     // Check the URL against Fanboy's Annoyance List.
5971                     String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
5972
5973                     // Process the Fanboy's Social Blocking List results.
5974                     if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) {  // The resource request matched Fanboy's Social Blocking List's blacklist.
5975                         // Add the result to the resource requests.
5976                         nestedScrollWebView.addResourceRequest(new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5977                                 fanboysSocialListResults[4], fanboysSocialListResults[5]});
5978
5979                         // Increment the blocked requests counters.
5980                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5981                         nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST);
5982
5983                         // Update the titles of the blocklist menu items if the WebView is currently displayed.
5984                         if (webViewDisplayed) {
5985                             // Updating the UI must be run from the UI thread.
5986                             activity.runOnUiThread(() -> {
5987                                 // Update the menu item titles.
5988                                 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5989
5990                                 // Update the options menu if it has been populated.
5991                                 if (optionsMenu != null) {
5992                                     optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5993                                     optionsFanboysSocialBlockingListMenuItem.setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
5994                                             getString(R.string.fanboys_social_blocking_list));
5995                                 }
5996                             });
5997                         }
5998
5999                         // The resource request was blocked.  Return an empty web resource response.
6000                         return emptyWebResourceResponse;
6001                     } else if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) {  // The resource request matched Fanboy's Social Blocking List's whitelist.
6002                         // Update the whitelist result string array tracker.
6003                         whitelistResultStringArray = new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
6004                                 fanboysSocialListResults[4], fanboysSocialListResults[5]};
6005                     }
6006                 }
6007
6008                 // Add the request to the log because it hasn't been processed by any of the previous checks.
6009                 if (whitelistResultStringArray != null) {  // The request was processed by a whitelist.
6010                     nestedScrollWebView.addResourceRequest(whitelistResultStringArray);
6011                 } else {  // The request didn't match any blocklist entry.  Log it as a default request.
6012                     nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_DEFAULT, url});
6013                 }
6014
6015                 // The resource request has not been blocked.  `return null` loads the requested resource.
6016                 return null;
6017             }
6018
6019             // Handle HTTP authentication requests.
6020             @Override
6021             public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
6022                 // Store the handler.
6023                 nestedScrollWebView.setHttpAuthHandler(handler);
6024
6025                 // Instantiate an HTTP authentication dialog.
6026                 DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.getWebViewFragmentId());
6027
6028                 // Show the HTTP authentication dialog.
6029                 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
6030             }
6031
6032             @Override
6033             public void onPageStarted(WebView view, String url, Bitmap favicon) {
6034                 // Get the app bar layout height.  This can't be done in `applyAppSettings()` because the app bar is not yet populated there.
6035                 // This should only be populated if it is greater than 0 because otherwise it will be reset to 0 if the app bar is hidden in full screen browsing mode.
6036                 if (appBarLayout.getHeight() > 0) appBarHeight = appBarLayout.getHeight();
6037
6038                 // Set the padding and layout settings according to the position of the app bar.
6039                 if (bottomAppBar) {  // The app bar is on the bottom.
6040                     // Adjust the UI.
6041                     if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {  // The app bar scrolls or full screen browsing mode is engaged with the app bar hidden.
6042                         // Reset the WebView padding to fill the available space.
6043                         swipeRefreshLayout.setPadding(0, 0, 0, 0);
6044                     } else {  // The app bar doesn't scroll or full screen browsing mode is not engaged with the app bar hidden.
6045                         // Move the WebView above the app bar layout.
6046                         swipeRefreshLayout.setPadding(0, 0, 0, appBarHeight);
6047                     }
6048                 } else {  // The app bar is on the top.
6049                     // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.  This can't be done in `appAppSettings()` because the app bar is not yet populated there.
6050                     if (scrollAppBar || (inFullScreenBrowsingMode && hideAppBar)) {
6051                         // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
6052                         swipeRefreshLayout.setPadding(0, 0, 0, 0);
6053
6054                         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
6055                         swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
6056                     } else {
6057                         // The swipe refresh layout must be manually moved below the app bar layout.
6058                         swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
6059
6060                         // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
6061                         swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
6062                     }
6063                 }
6064
6065                 // Reset the list of resource requests.
6066                 nestedScrollWebView.clearResourceRequests();
6067
6068                 // Reset the requests counters.
6069                 nestedScrollWebView.resetRequestsCounters();
6070
6071                 // Get the current page position.
6072                 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
6073
6074                 // Update the URL text bar if the page is currently selected and the URL edit text is not currently being edited.
6075                 if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus()) {
6076                     // Display the formatted URL text.
6077                     urlEditText.setText(url);
6078
6079                     // Apply text highlighting to the URL text box.
6080                     highlightUrlText();
6081
6082                     // Hide the keyboard.
6083                     inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
6084                 }
6085
6086                 // Reset the list of host IP addresses.
6087                 nestedScrollWebView.setCurrentIpAddresses("");
6088
6089                 // Get a URI for the current URL.
6090                 Uri currentUri = Uri.parse(url);
6091
6092                 // Get the IP addresses for the host.
6093                 new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
6094
6095                 // Replace Refresh with Stop if the options menu has been created.  (The first WebView typically begins loading before the menu items are instantiated.)
6096                 if (optionsMenu != null) {
6097                     // Set the title.
6098                     optionsRefreshMenuItem.setTitle(R.string.stop);
6099
6100                     // Get the app bar and theme preferences.
6101                     boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
6102
6103                     // Set the icon if it is displayed in the AppBar.  Once the minimum API is >= 26, the blue and black icons can be combined with a tint list.
6104                     if (displayAdditionalAppBarIcons) {
6105                         optionsRefreshMenuItem.setIcon(R.drawable.close_blue);
6106                     }
6107                 }
6108             }
6109
6110             @Override
6111             public void onPageFinished(WebView view, String url) {
6112                 // Flush any cookies to persistent storage.  The cookie manager has become very lazy about flushing cookies in recent versions.
6113                 if (nestedScrollWebView.getAcceptCookies()) {
6114                     CookieManager.getInstance().flush();
6115                 }
6116
6117                 // Update the Refresh menu item if the options menu has been created.
6118                 if (optionsMenu != null) {
6119                     // Reset the Refresh title.
6120                     optionsRefreshMenuItem.setTitle(R.string.refresh);
6121
6122                     // Get the app bar and theme preferences.
6123                     boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
6124
6125                     // If the icon is displayed in the app bar, reset it according to the theme.
6126                     if (displayAdditionalAppBarIcons) {
6127                         // Set the icon.
6128                         optionsRefreshMenuItem.setIcon(R.drawable.refresh_enabled);
6129                     }
6130                 }
6131
6132                  // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
6133                 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
6134                 String privateDataDirectoryString = getApplicationInfo().dataDir;
6135
6136                 // Clear the cache, history, and logcat if Incognito Mode is enabled.
6137                 if (incognitoModeEnabled) {
6138                     // Clear the cache.  `true` includes disk files.
6139                     nestedScrollWebView.clearCache(true);
6140
6141                     // Clear the back/forward history.
6142                     nestedScrollWebView.clearHistory();
6143
6144                     // Manually delete cache folders.
6145                     try {
6146                         // Delete the main cache directory.
6147                         Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache");
6148                     } catch (IOException exception) {
6149                         // Do nothing if an error is thrown.
6150                     }
6151
6152                     // Clear the logcat.
6153                     try {
6154                         // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
6155                         Runtime.getRuntime().exec("logcat -b all -c");
6156                     } catch (IOException exception) {
6157                         // Do nothing.
6158                     }
6159                 }
6160
6161                 // Clear the `Service Worker` directory.
6162                 try {
6163                     // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
6164                     Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Default/Service Worker/"});
6165                 } catch (IOException exception) {
6166                     // Do nothing.
6167                 }
6168
6169                 // Get the current page position.
6170                 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
6171
6172                 // Get the current URL from the nested scroll WebView.  This is more accurate than using the URL passed into the method, which is sometimes not the final one.
6173                 String currentUrl = nestedScrollWebView.getUrl();
6174
6175                 // Get the current tab.
6176                 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
6177
6178                 // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
6179                 // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
6180                 // Probably some sort of race condition when Privacy Browser is being resumed.
6181                 if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) {
6182                     // Check to see if the URL is `about:blank`.
6183                     if (currentUrl.equals("about:blank")) {  // The WebView is blank.
6184                         // Display the hint in the URL edit text.
6185                         urlEditText.setText("");
6186
6187                         // Request focus for the URL text box.
6188                         urlEditText.requestFocus();
6189
6190                         // Display the keyboard.
6191                         inputMethodManager.showSoftInput(urlEditText, 0);
6192
6193                         // Apply the domain settings.  This clears any settings from the previous domain.
6194                         applyDomainSettings(nestedScrollWebView, "", true, false, false);
6195
6196                         // Only populate the title text view if the tab has been fully created.
6197                         if (tab != null) {
6198                             // Get the custom view from the tab.
6199                             View tabView = tab.getCustomView();
6200
6201                             // Remove the incorrect warning below that the current tab view might be null.
6202                             assert tabView != null;
6203
6204                             // Get the title text view from the tab.
6205                             TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
6206
6207                             // Set the title as the tab text.
6208                             tabTitleTextView.setText(R.string.new_tab);
6209                         }
6210                     } else {  // The WebView has loaded a webpage.
6211                         // Update the URL edit text if it is not currently being edited.
6212                         if (!urlEditText.hasFocus()) {
6213                             // Sanitize the current URL.  This removes unwanted URL elements that were added by redirects, so that they won't be included if the URL is shared.
6214                             String sanitizedUrl = sanitizeUrl(currentUrl);
6215
6216                             // Display the final URL.  Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
6217                             urlEditText.setText(sanitizedUrl);
6218
6219                             // Apply text highlighting to the URL.
6220                             highlightUrlText();
6221                         }
6222
6223                         // Only populate the title text view if the tab has been fully created.
6224                         if (tab != null) {
6225                             // Get the custom view from the tab.
6226                             View tabView = tab.getCustomView();
6227
6228                             // Remove the incorrect warning below that the current tab view might be null.
6229                             assert tabView != null;
6230
6231                             // Get the title text view from the tab.
6232                             TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
6233
6234                             // Set the title as the tab text.  Sometimes `onReceivedTitle()` is not called, especially when navigating history.
6235                             tabTitleTextView.setText(nestedScrollWebView.getTitle());
6236                         }
6237                     }
6238                 }
6239             }
6240
6241             // Handle SSL Certificate errors.  Suppress the lint warning that ignoring the error might be dangerous.
6242             @SuppressLint("WebViewClientOnReceivedSslError")
6243             @Override
6244             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
6245                 // Get the current website SSL certificate.
6246                 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
6247
6248                 // Extract the individual pieces of information from the current website SSL certificate.
6249                 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
6250                 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
6251                 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
6252                 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
6253                 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
6254                 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
6255                 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
6256                 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
6257
6258                 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
6259                 if (nestedScrollWebView.hasPinnedSslCertificate()) {
6260                     // Get the pinned SSL certificate.
6261                     Pair<String[], Date[]> pinnedSslCertificatePair = nestedScrollWebView.getPinnedSslCertificate();
6262
6263                     // Extract the arrays from the array list.
6264                     String[] pinnedSslCertificateStringArray = pinnedSslCertificatePair.getFirst();
6265                     Date[] pinnedSslCertificateDateArray = pinnedSslCertificatePair.getSecond();
6266
6267                     // Check if the current SSL certificate matches the pinned certificate.
6268                     if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&
6269                         currentWebsiteIssuedToUName.equals(pinnedSslCertificateStringArray[2]) && currentWebsiteIssuedByCName.equals(pinnedSslCertificateStringArray[3]) &&
6270                         currentWebsiteIssuedByOName.equals(pinnedSslCertificateStringArray[4]) && currentWebsiteIssuedByUName.equals(pinnedSslCertificateStringArray[5]) &&
6271                         currentWebsiteSslStartDate.equals(pinnedSslCertificateDateArray[0]) && currentWebsiteSslEndDate.equals(pinnedSslCertificateDateArray[1])) {
6272
6273                         // An SSL certificate is pinned and matches the current domain certificate.  Proceed to the website without displaying an error.
6274                         handler.proceed();
6275                     }
6276                 } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
6277                     // Store the SSL error handler.
6278                     nestedScrollWebView.setSslErrorHandler(handler);
6279
6280                     // Instantiate an SSL certificate error alert dialog.
6281                     DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
6282
6283                     // Try to show the dialog.  The SSL error handler continues to function even when the WebView is paused.  Attempting to display a dialog in that state leads to a crash.
6284                     try {
6285                         // Show the SSL certificate error dialog.
6286                         sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
6287                     } catch (Exception exception) {
6288                         // Add the dialog to the pending dialog array list.  It will be displayed in `onStart()`.
6289                         pendingDialogsArrayList.add(new PendingDialog(sslCertificateErrorDialogFragment, getString(R.string.ssl_certificate_error)));
6290                     }
6291                 }
6292             }
6293         });
6294
6295         // Check to see if the state is being restored.
6296         if (restoringState) {  // The state is being restored.
6297             // Resume the nested scroll WebView JavaScript timers.
6298             nestedScrollWebView.resumeTimers();
6299         } else if (pageNumber == 0) {  // The first page is being loaded.
6300             // Set this nested scroll WebView as the current WebView.
6301             currentWebView = nestedScrollWebView;
6302
6303             // Initialize the URL to load string.
6304             String urlToLoadString;
6305
6306             // Get the intent that started the app.
6307             Intent launchingIntent = getIntent();
6308
6309             // Reset the intent.  This prevents a duplicate tab from being created on restart.
6310             setIntent(new Intent());
6311
6312             // Get the information from the intent.
6313             String launchingIntentAction = launchingIntent.getAction();
6314             Uri launchingIntentUriData = launchingIntent.getData();
6315             String launchingIntentStringExtra = launchingIntent.getStringExtra(Intent.EXTRA_TEXT);
6316
6317             // Parse the launching intent URL.
6318             if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {  // The intent contains a search string.
6319                 // Create an encoded URL string.
6320                 String encodedUrlString;
6321
6322                 // Sanitize the search input and convert it to a search.
6323                 try {
6324                     encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
6325                 } catch (UnsupportedEncodingException exception) {
6326                     encodedUrlString = "";
6327                 }
6328
6329                 // Store the web search as the URL to load.
6330                 urlToLoadString = searchURL + encodedUrlString;
6331             } else if (launchingIntentUriData != null) {  // The launching intent contains a URL formatted as a URI.
6332                 // Store the URI as a URL.
6333                 urlToLoadString = launchingIntentUriData.toString();
6334             } else if (launchingIntentStringExtra != null) {  // The launching intent contains text that might be a URL.
6335                 // Store the URL.
6336                 urlToLoadString = launchingIntentStringExtra;
6337             } else if (!url.equals("")) {  // The activity has been restarted.
6338                 // Load the saved URL.
6339                 urlToLoadString = url;
6340             } else {  // The is no URL in the intent.
6341                 // Store the homepage to be loaded.
6342                 urlToLoadString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
6343             }
6344
6345             // Load the website if not waiting for the proxy.
6346             if (waitingForProxy) {  // Store the URL to be loaded in the Nested Scroll WebView.
6347                 nestedScrollWebView.setWaitingForProxyUrlString(urlToLoadString);
6348             } else {  // Load the URL.
6349                 loadUrl(nestedScrollWebView, urlToLoadString);
6350             }
6351
6352             // Reset the intent.  This prevents a duplicate tab from being created on a subsequent restart if loading an link from a new intent on restart.
6353             // For example, this prevents a duplicate tab if a link is loaded from the Guide after changing the theme in the guide and then changing the theme again in the main activity.
6354             setIntent(new Intent());
6355         } else {  // This is not the first tab.
6356             // Load the URL.
6357             loadUrl(nestedScrollWebView, url);
6358
6359             // Set the focus and display the keyboard if the URL is blank.
6360             if (url.equals("")) {
6361                 // Request focus for the URL text box.
6362                 urlEditText.requestFocus();
6363
6364                 // Create a display keyboard handler.
6365                 Handler displayKeyboardHandler = new Handler();
6366
6367                 // Create a display keyboard runnable.
6368                 Runnable displayKeyboardRunnable = () -> {
6369                     // Display the keyboard.
6370                     inputMethodManager.showSoftInput(urlEditText, 0);
6371                 };
6372
6373                 // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition.
6374                 displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100);
6375             }
6376         }
6377     }
6378 }