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