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