]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Flush any cookies to storage every time a web page finishes loading. Fixes https...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index 33ae5b2d326958c859c9296830cdfafc0cc817b4..0158996f657e02aad3377bfe3fbb9d198acd47cd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2015-2017 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
  *
  * Download cookie code contributed 2017 Hendrik Knackstedt.  Copyright assigned to Soren Stoutner <soren@stoutner.com>.
  *
@@ -34,6 +34,8 @@ import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.database.Cursor;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Typeface;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -41,15 +43,20 @@ import android.net.http.SslCertificate;
 import android.net.http.SslError;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.preference.PreferenceManager;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintManager;
 import android.support.annotation.NonNull;
 import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.FloatingActionButton;
 import android.support.design.widget.NavigationView;
 import android.support.design.widget.Snackbar;
-import android.support.v4.app.ActivityCompat;
 import android.support.v4.content.ContextCompat;
+// `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
+import android.support.v4.content.pm.ShortcutInfoCompat;
+import android.support.v4.content.pm.ShortcutManagerCompat;
+import android.support.v4.graphics.drawable.IconCompat;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v4.widget.SwipeRefreshLayout;
@@ -70,10 +77,11 @@ import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
 import android.webkit.CookieManager;
-import android.webkit.DownloadListener;
+import android.webkit.HttpAuthHandler;
 import android.webkit.SslErrorHandler;
 import android.webkit.WebBackForwardList;
 import android.webkit.WebChromeClient;
@@ -82,61 +90,79 @@ import android.webkit.WebStorage;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.webkit.WebViewDatabase;
+import android.widget.CursorAdapter;
 import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.ListView;
 import android.widget.ProgressBar;
+import android.widget.RadioButton;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import com.stoutner.privacybrowser.BannerAd;
 import com.stoutner.privacybrowser.BuildConfig;
 import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.dialogs.AddDomainDialog;
+import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
+import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
+import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
+import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
+import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
 import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
+import com.stoutner.privacybrowser.helpers.BlockListHelper;
+import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
 
-import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
-public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener,
-        PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener {
-
-    // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`,
-    // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`,
-    // `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
+public class MainWebViewActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener, CreateBookmarkDialog.CreateBookmarkListener,
+        CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, DownloadFileDialog.DownloadFileListener,
+        DownloadImageDialog.DownloadImageListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener,
+        NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener,
+        UrlHistoryDialog.UrlHistoryListener {
+
+    // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`,
+    // `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`,
+    // `EditBookmarkDatabaseViewDialog`, `HttpAuthenticationDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`, `CreateHomeScreenShortcutDialog`,
+    //  and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
     public static boolean darkTheme;
 
-    // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`,
-    // and `ViewSslCertificateDialog`.  It is also used in `onCreate()`, `onCreateHomeScreenShortcutCreate()`, and `applyDomainSettings`.
+    // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
+    // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`.  It is also used in `onCreate()`,
+    // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
     public static Bitmap favoriteIconBitmap;
 
     // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
     public static String formattedUrlString;
 
-    // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`, and `ViewSslCertificateDialog`.  It is also used in `onCreate()`.
+    // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`,
+    // and `ViewSslCertificateDialog`.  It is also used in `onCreate()`.
     public static SslCertificate sslCertificate;
 
     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`.
@@ -145,14 +171,32 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`.  It is also used in `onCreate()`.
     public static String webViewTitle;
 
-    // `displayWebpageImagesBoolean` is public static so it can be accessed from `DomainSettingsFragment`.  It is also used in `applyAppSettings()` and `applyDomainSettings()`.
-    public static boolean displayWebpageImagesBoolean;
+    // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`.  It is also used in `applyDomainSettings()`.
+    public static String appliedUserAgentString;
 
-    // `reloadOnRestartBoolean` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`
-    public static boolean reloadOnRestartBoolean;
+    // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`
+    public static boolean reloadOnRestart;
 
-    // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`.  They are also used in `onCreate()` and `applyDomainSettings()`.
+    // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`.
+    public static boolean loadUrlOnRestart;
+
+    // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onRestart()`.
+    public static boolean restartFromBookmarksActivity;
+
+    // The block list versions are public static so they can be accessed from `AboutTabFragment`.  They are also used in `onCreate()`.
+    public static String easyListVersion;
+    public static String easyPrivacyVersion;
+    public static String fanboyAnnoyanceVersion;
+    public static String fanboySocialVersion;
+
+    // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`.  It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
+    // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
+    public static String currentBookmarksFolder;
+
+    // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`.  It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
     public static int domainSettingsDatabaseId;
+
+    // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`.  They are also used in `onCreate()` and `applyDomainSettings()`.
     public static String pinnedDomainSslIssuedToCNameString;
     public static String pinnedDomainSslIssuedToONameString;
     public static String pinnedDomainSslIssuedToUNameString;
@@ -172,14 +216,14 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
     private Bitmap favoriteIconDefaultBitmap;
 
-    // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
+    // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
     private DrawerLayout drawerLayout;
 
     // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
     private CoordinatorLayout rootCoordinatorLayout;
 
-    // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`
-    // `onSslMismatchBack()`, and `setDisplayWebpageImages()`.
+    // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
+    // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `setDisplayWebpageImages()`.
     private WebView mainWebView;
 
     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
@@ -200,33 +244,41 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
     private final Map<String, String> customHeaders = new HashMap<>();
 
-    // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applyAppSettings()`.
-    // It is `Boolean` instead of `boolean` because `applyAppSettings()` needs to know if it is `null`.
-    private Boolean javaScriptEnabled;
+    // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
+    private boolean javaScriptEnabled;
 
-    // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyAppSettings()`.
+    // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
     private boolean firstPartyCookiesEnabled;
 
-    // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
+    // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
     private boolean thirdPartyCookiesEnabled;
 
-    // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
+    // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
     private boolean domStorageEnabled;
 
-    // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
+    // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
     private boolean saveFormDataEnabled;
 
+    // `nightMode` is used in `onCreate()` and  `applyDomainSettings()`.
+    private boolean nightMode;
+
     // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applyAppSettings()`.
     private boolean swipeToRefreshEnabled;
 
+    // `displayWebpageImagesBoolean` is used in `applyAppSettings()` and `applyDomainSettings()`.
+    private boolean displayWebpageImagesBoolean;
+
     // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applyAppSettings()`.
     private String homepage;
 
     // `searchURL` is used in `loadURLFromTextBox()` and `applyAppSettings()`.
     private String searchURL;
 
-    // `adBlockerEnabled` is used in `onCreate()` and `applyAppSettings()`.
-    private boolean adBlockerEnabled;
+    // The block list variables are used in `onCreate()` and `applyAppSettings()`.
+    private boolean easyListEnabled;
+    private boolean easyPrivacyEnabled;
+    private boolean fanboyAnnoyanceListEnabled;
+    private boolean fanboySocialBlockingListEnabled;
 
     // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
     private Runtime privacyBrowserRuntime;
@@ -246,7 +298,10 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
     private boolean translucentNavigationBarOnFullscreen;
 
-    // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
+    // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
+    private boolean reapplyDomainSettingsOnRestart;
+
+    // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
     private String currentDomainName;
 
     // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
@@ -255,7 +310,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `waitingForOrbot` is used in `onCreate()` and `applyAppSettings()`.
     private boolean waitingForOrbot;
 
-    // `domainSettingsApplied` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
+    // `domainSettingsApplied` is used in `prepareOptionsMenu()`, `applyDomainSettings()`, and `setDisplayWebpageImages()`.
     private boolean domainSettingsApplied;
 
     // `displayWebpageImagesInt` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
@@ -288,13 +343,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
     private EditText urlTextBox;
 
-    // `redColorSpan` is used in `onCreate()` and `highlightUrlText()`.
+    // The color spans are used in `onCreate()` and `highlightUrlText()`.
     private ForegroundColorSpan redColorSpan;
-
-    // `initialGrayColorSpan` is sued in `onCreate()` and `highlightUrlText()`.
     private ForegroundColorSpan initialGrayColorSpan;
-
-    // `finalGrayColorSpam` is used in `onCreate()` and `highlightUrlText()`.
     private ForegroundColorSpan finalGrayColorSpan;
 
     // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
@@ -303,6 +354,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
     private SslErrorHandler sslErrorHandler;
 
+    // `httpAuthHandler` is used in `onCreate()`, `onHttpAuthenticationCancel()`, and `onHttpAuthenticationProceed()`.
+    private static HttpAuthHandler httpAuthHandler;
+
     // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
     private InputMethodManager inputMethodManager;
 
@@ -315,10 +369,27 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
     private boolean pinnedDomainSslCertificate;
 
+    // `bookmarksDatabaseHelper` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
+    private BookmarksDatabaseHelper bookmarksDatabaseHelper;
+
+    // `bookmarksListView` is used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, and `loadBookmarksFolder()`.
+    private ListView bookmarksListView;
+
+    // `bookmarksTitleTextView` is used in `onCreate()` and `loadBookmarksFolder()`.
+    private TextView bookmarksTitleTextView;
+
+    // `bookmarksCursor` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
+    private Cursor bookmarksCursor;
+
+    // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
+    private CursorAdapter bookmarksCursorAdapter;
+
+    // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
+    private String oldFolderNameString;
 
     @Override
     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.  The whole premise of Privacy Browser is built around an understanding of these dangers.
-    @SuppressLint("SetJavaScriptEnabled")
+    @SuppressLint({"SetJavaScriptEnabled"})
     // Remove Android Studio's warning about deprecations.  We have to use the deprecated `getColor()` until API >= 23.
     @SuppressWarnings("deprecation")
     protected void onCreate(Bundle savedInstanceState) {
@@ -344,8 +415,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // Get a handle for `inputMethodManager`.
         inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-        // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
-        supportAppBar = (Toolbar) findViewById(R.id.app_bar);
+        // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
+        supportAppBar = findViewById(R.id.app_bar);
         setSupportActionBar(supportAppBar);
         appBar = getSupportActionBar();
 
@@ -356,48 +427,42 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         appBar.setCustomView(R.layout.url_app_bar);
         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
-        // Initialize the `ForegroundColorSpans` and `StyleSpan` for highlighting `urlTextBox`.  We have to use the deprecated `getColor()` until API >= 23.
+        // Initialize the foreground color spans for highlighting the URLs.  We have to use the deprecated `getColor()` until API >= 23.
         redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
         initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
         finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
 
         // Get a handle for `urlTextBox`.
-        urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.url_edittext);
+        urlTextBox = findViewById(R.id.url_edittext);
 
         // Remove the formatting from `urlTextBar` when the user is editing the text.
-        urlTextBox.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                if (hasFocus) {  // The user is editing `urlTextBox`.
-                    // Remove the highlighting.
-                    urlTextBox.getText().removeSpan(redColorSpan);
-                    urlTextBox.getText().removeSpan(initialGrayColorSpan);
-                    urlTextBox.getText().removeSpan(finalGrayColorSpan);
-                } else {  // The user has stopped editing `urlTextBox`.
-                    // Reapply the highlighting.
-                    highlightUrlText();
-                }
+        urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
+            if (hasFocus) {  // The user is editing `urlTextBox`.
+                // Remove the highlighting.
+                urlTextBox.getText().removeSpan(redColorSpan);
+                urlTextBox.getText().removeSpan(initialGrayColorSpan);
+                urlTextBox.getText().removeSpan(finalGrayColorSpan);
+            } else {  // The user has stopped editing `urlTextBox`.
+                // Reapply the highlighting.
+                highlightUrlText();
             }
         });
 
-        // Set the `Go` button on the keyboard to load the URL in `urlTextBox`.
-        urlTextBox.setOnKeyListener(new View.OnKeyListener() {
-            @Override
-            public boolean onKey(View v, int keyCode, KeyEvent event) {
-                // If the event is a key-down event on the `enter` button, load the URL.
-                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
-                    // Load the URL into the mainWebView and consume the event.
-                    try {
-                        loadUrlFromTextBox();
-                    } catch (UnsupportedEncodingException e) {
-                        e.printStackTrace();
-                    }
-                    // If the enter key was pressed, consume the event.
-                    return true;
-                } else {
-                    // If any other key was pressed, do not consume the event.
-                    return false;
+        // Set the go button on the keyboard to load the URL in `urlTextBox`.
+        urlTextBox.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
+            // If the event is a key-down event on the `enter` button, load the URL.
+            if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+                // Load the URL into the mainWebView and consume the event.
+                try {
+                    loadUrlFromTextBox();
+                } catch (UnsupportedEncodingException e) {
+                    e.printStackTrace();
                 }
+                // If the enter key was pressed, consume the event.
+                return true;
+            } else {
+                // If any other key was pressed, do not consume the event.
+                return false;
             }
         });
 
@@ -431,15 +496,59 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
 
         // Get handles for views that need to be accessed.
-        drawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout);
-        rootCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.root_coordinatorlayout);
-        mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.main_webview_relativelayout);
-        mainWebView = (WebView) findViewById(R.id.main_webview);
-        findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
-        findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext);
-        fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.full_screen_video_framelayout);
-        urlAppBarRelativeLayout = (RelativeLayout) findViewById(R.id.url_app_bar_relativelayout);
-        favoriteIconImageView = (ImageView) findViewById(R.id.favorite_icon);
+        drawerLayout = findViewById(R.id.drawerlayout);
+        rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
+        bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
+        bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
+        FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
+        FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
+        FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
+        mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
+        mainWebView = findViewById(R.id.main_webview);
+        findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+        findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+        fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
+        urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
+        favoriteIconImageView = findViewById(R.id.favorite_icon);
+
+        // 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.
+        if (darkTheme) {
+            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
+            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
+            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
+            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
+        } else {
+            launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
+            createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
+            createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
+            bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
+        }
+
+        // Set the launch bookmarks activity FAB to launch the bookmarks activity.
+        launchBookmarksActivityFab.setOnClickListener(v -> {
+            // Create an intent to launch the bookmarks activity.
+            Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
+
+            // Include the current folder with the `Intent`.
+            bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
+
+            // Make it so.
+            startActivity(bookmarksIntent);
+        });
+
+        // Set the create new bookmark folder FAB to display the `AlertDialog`.
+        createBookmarkFolderFab.setOnClickListener(v -> {
+            // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
+            AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
+            createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder));
+        });
+
+        // Set the create new bookmark FAB to display the `AlertDialog`.
+        createBookmarkFab.setOnClickListener(view -> {
+            // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
+            AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
+            createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
+        });
 
         // Create a double-tap listener to toggle full-screen mode.
         final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@@ -469,7 +578,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
                             /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
                              * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
+                             * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
                              */
                             rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
 
@@ -479,7 +588,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                             // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
                             rootCoordinatorLayout.setFitsSystemWindows(false);
 
-                            if (translucentNavigationBarOnFullscreen) {  // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
+                            // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
+                            if (translucentNavigationBarOnFullscreen) {
                                 // Set the navigation bar to be translucent.
                                 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
                             }
@@ -519,12 +629,12 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         });
 
         // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
-        mainWebView.setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                // Send the `event` to `gestureDetector`.
-                return gestureDetector.onTouchEvent(event);
-            }
+        mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
+            // Call `performClick()` on the view, which is required for accessibility.
+            v.performClick();
+
+            // Send the `event` to `gestureDetector`.
+            return gestureDetector.onTouchEvent(event);
         });
 
         // Update `findOnPageCountTextView`.
@@ -541,8 +651,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     // `activeMatchOrdinal` is zero-based.
                     int activeMatch = activeMatchOrdinal + 1;
 
+                    // Build the match string.
+                    String matchString = activeMatch + "/" + numberOfMatches;
+
                     // Set `findOnPageCountTextView`.
-                    findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches);
+                    findOnPageCountTextView.setText(matchString);
                 }
             }
         });
@@ -567,37 +680,30 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         });
 
         // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
-        findOnPageEditText.setOnKeyListener(new View.OnKeyListener() {
-            @Override
-            public boolean onKey(View v, int keyCode, KeyEvent event) {
-                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
-                    // Hide the soft keyboard.  `0` indicates no additional flags.
-                    inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+        findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
+            if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
+                // Hide the soft keyboard.  `0` indicates no additional flags.
+                inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
 
-                    // Consume the event.
-                    return true;
-                } else {  // A different key was pressed.
-                    // Do not consume the event.
-                    return false;
-                }
+                // Consume the event.
+                return true;
+            } else {  // A different key was pressed.
+                // Do not consume the event.
+                return false;
             }
         });
 
         // Implement swipe to refresh
-        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refreshlayout);
+        swipeRefreshLayout = findViewById(R.id.swipe_refreshlayout);
         swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
-        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
-            @Override
-            public void onRefresh() {
-                mainWebView.reload();
-            }
-        });
+        swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
 
-        // `DrawerTitle` identifies the `DrawerLayout` in accessibility mode.
+        // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
+        drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
 
         // Listen for touches on the navigation menu.
-        final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationview);
+        final NavigationView navigationView = findViewById(R.id.navigationview);
         navigationView.setNavigationItemSelectedListener(this);
 
         // Get handles for `navigationMenu` and the back and forward menu items.  The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu.
@@ -606,18 +712,79 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
         final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
 
+        // Initialize the bookmarks database helper.  `this` specifies the context.  The two `nulls` do not specify the database name or a `CursorFactory`.
+        // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
+        bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
+
+        // Initialize `currentBookmarksFolder`.  `""` is the home folder in the database.
+        currentBookmarksFolder = "";
+
+        // Load the home folder, which is `""` in the database.
+        loadBookmarksFolder();
+
+        bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
+            // Convert the id from long to int to match the format of the bookmarks database.
+            int databaseID = (int) id;
+
+            // Get the bookmark `Cursor` for this ID and move it to the first row.
+            Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
+            bookmarkCursor.moveToFirst();
+
+            // Act upon the bookmark according to the type.
+            if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {  // The selected bookmark is a folder.
+                // Store the new folder name in `currentBookmarksFolder`.
+                currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+
+                // Load the new folder.
+                loadBookmarksFolder();
+            } else {  // The selected bookmark is not a folder.
+                // Load the bookmark URL.
+                loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
+
+                // Close the bookmarks drawer.
+                drawerLayout.closeDrawer(GravityCompat.END);
+            }
+
+            // Close the `Cursor`.
+            bookmarkCursor.close();
+        });
+
+        bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
+            // Convert the database ID from `long` to `int`.
+            int databaseId = (int) id;
+
+            // Find out if the selected bookmark is a folder.
+            boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
+
+            if (isFolder) {
+                // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
+                oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+
+                // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
+                AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
+                editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
+            } else {
+                // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
+                AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
+                editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
+            }
+
+            // Consume the event.
+            return true;
+        });
+
         // The `DrawerListener` allows us to update the Navigation Menu.
         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
             @Override
-            public void onDrawerSlide(View drawerView, float slideOffset) {
+            public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
             }
 
             @Override
-            public void onDrawerOpened(View drawerView) {
+            public void onDrawerOpened(@NonNull View drawerView) {
             }
 
             @Override
-            public void onDrawerClosed(View drawerView) {
+            public void onDrawerClosed(@NonNull View drawerView) {
             }
 
             @Override
@@ -640,36 +807,238 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // drawerToggle creates the hamburger icon at the start of the AppBar.
         drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
 
-        // Initialize `adServerSet`.
-        final Set<String> adServersSet = new HashSet<>();
+        // Get a handle for the progress bar.
+        final ProgressBar progressBar = findViewById(R.id.progress_bar);
+
+        mainWebView.setWebChromeClient(new WebChromeClient() {
+            // Update the progress bar when a page is loading.
+            @Override
+            public void onProgressChanged(WebView view, int progress) {
+                // Inject the night mode CSS if night mode is enabled.
+                if (nightMode) {
+                    // `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
+                    // used by WordPress.  `text-decoration: none` removes all text underlines.  `text-shadow: none` removes text shadows, which usually have a hard coded color.
+                    // `border: none` removes all borders, which can also be used to underline text.
+                    // `a {color: #1565C0}` sets links to be a dark blue.  `!important` takes precedent over any existing sub-settings.
+                    mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
+                            "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
+                            "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> {
+                                // Initialize a `Handler` to display `mainWebView`.
+                                Handler displayWebViewHandler = new Handler();
+
+                                // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied.
+                                Runnable displayWebViewRunnable = () -> {
+                                    // Only display `mainWebView` if the progress bar is one.  This prevents the display of the `WebView` while it is still loading.
+                                    if (progressBar.getVisibility() == View.GONE) {
+                                        mainWebView.setVisibility(View.VISIBLE);
+                                    }
+                                };
+
+                                // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds.
+                                displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
+                            });
+                }
+
+                // Update the progress bar.
+                progressBar.setProgress(progress);
+
+                // Set the visibility of the progress bar.
+                if (progress < 100) {
+                    // Show the progress bar.
+                    progressBar.setVisibility(View.VISIBLE);
+                } else {
+                    // Hide the progress bar.
+                    progressBar.setVisibility(View.GONE);
+
+                    // Display `mainWebView` if night mode is disabled.
+                    // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not
+                    // currently enabled.
+                    if (!nightMode) {
+                        mainWebView.setVisibility(View.VISIBLE);
+                    }
+
+                    //Stop the `SwipeToRefresh` indicator if it is running
+                    swipeRefreshLayout.setRefreshing(false);
+                }
+            }
+
+            // Set the favorite icon when it changes.
+            @Override
+            public void onReceivedIcon(WebView view, Bitmap icon) {
+                // Only update the favorite icon if the website has finished loading.
+                if (progressBar.getVisibility() == View.GONE) {
+                    // Save a copy of the favorite icon.
+                    favoriteIconBitmap = icon;
+
+                    // Place the favorite icon in the appBar.
+                    favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
+                }
+            }
+
+            // Save a copy of the title when it changes.
+            @Override
+            public void onReceivedTitle(WebView view, String title) {
+                // Save a copy of the title.
+                webViewTitle = title;
+            }
+
+            // Enter full screen video
+            @Override
+            public void onShowCustomView(View view, CustomViewCallback callback) {
+                // Pause the ad if this is the free flavor.
+                if (BuildConfig.FLAVOR.contentEquals("free")) {
+                    BannerAd.pauseAd(adView);
+                }
 
-        // Load the list of ad servers into memory.
-        try {
-            // Load `pgl.yoyo.org_adservers.txt` into a `BufferedReader`.
-            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("pgl.yoyo.org_adservers.txt")));
+                // Remove the translucent overlays.
+                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+                // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
+                drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+
+                /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+                 */
+                rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
 
-            // Create a string for storing each ad server.
-            String adServer;
+                // Set `rootCoordinatorLayout` to fill the entire screen.
+                rootCoordinatorLayout.setFitsSystemWindows(false);
 
-            // Populate `adServersSet`.
-            while ((adServer = bufferedReader.readLine()) != null) {
-                adServersSet.add(adServer);
+                // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
+                fullScreenVideoFrameLayout.addView(view);
+                fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
             }
 
-            // Close `bufferedReader`.
-            bufferedReader.close();
-        } catch (IOException ioException) {
-            // We're pretty sure the asset exists, so we don't need to worry about the `IOException` ever being thrown.
+            // Exit full screen video
+            public void onHideCustomView() {
+                // Hide `fullScreenVideoFrameLayout`.
+                fullScreenVideoFrameLayout.removeAllViews();
+                fullScreenVideoFrameLayout.setVisibility(View.GONE);
+
+                // Add the translucent status flag.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
+                getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+                // Set `rootCoordinatorLayout` to fit inside the status and navigation bars.  This also clears the `SYSTEM_UI` flags.
+                rootCoordinatorLayout.setFitsSystemWindows(true);
+
+                // Show the ad if this is the free flavor.
+                if (BuildConfig.FLAVOR.contentEquals("free")) {
+                    // Reload the ad.  Because the screen may have rotated, we need to use `reloadAfterRotate`.
+                    BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
+
+                    // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
+                    adView = findViewById(R.id.adview);
+                }
+            }
+        });
+
+        // Register `mainWebView` for a context menu.  This is used to see link targets and download images.
+        registerForContextMenu(mainWebView);
+
+        // Allow the downloading of files.
+        mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
+            // Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`.
+            AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+            downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+        });
+
+        // Allow pinch to zoom.
+        mainWebView.getSettings().setBuiltInZoomControls(true);
+
+        // Hide zoom controls.
+        mainWebView.getSettings().setDisplayZoomControls(false);
+
+        // Set `mainWebView` to use a wide viewport.  Otherwise, some web pages will be scrunched and some content will render outside the screen.
+        mainWebView.getSettings().setUseWideViewPort(true);
+
+        // Set `mainWebView` to load in overview mode (zoomed out to the maximum width).
+        mainWebView.getSettings().setLoadWithOverviewMode(true);
+
+        // Explicitly disable geolocation.
+        mainWebView.getSettings().setGeolocationEnabled(false);
+
+        // Initialize cookieManager.
+        cookieManager = CookieManager.getInstance();
+
+        // 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).
+        customHeaders.put("X-Requested-With", "");
+
+        // Initialize the default preference values the first time the program is run.  `this` is the context.  `false` keeps this command from resetting any current preferences back to default.
+        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
+
+        // Get the intent that started the app.
+        final Intent launchingIntent = getIntent();
+
+        // Extract the launching intent data as `launchingIntentUriData`.
+        final Uri launchingIntentUriData = launchingIntent.getData();
+
+        // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`.
+        if (launchingIntentUriData != null) {
+            formattedUrlString = launchingIntentUriData.toString();
         }
 
+        // Get a handle for the `Runtime`.
+        privacyBrowserRuntime = Runtime.getRuntime();
+
+        // Store the application's private data directory.
+        privateDataDirectoryString = getApplicationInfo().dataDir;
+        // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+
+        // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
+        inFullScreenBrowsingMode = false;
+
+        // Initialize AdView for the free flavor.
+        adView = findViewById(R.id.adview);
+
+        // Initialize the privacy settings variables.
+        javaScriptEnabled = false;
+        firstPartyCookiesEnabled = false;
+        thirdPartyCookiesEnabled = false;
+        domStorageEnabled = false;
+        saveFormDataEnabled = false;
+        nightMode = false;
+
+        // Initialize `webViewTitle`.
+        webViewTitle = getString(R.string.no_title);
+
+        // Initialize `favoriteIconBitmap`.  `ContextCompat` must be used until API >= 21.
+        Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
+        BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
+        assert favoriteIconBitmapDrawable != null;
+        favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
+
+        // If the favorite icon is null, load the default.
+        if (favoriteIconBitmap == null) {
+            favoriteIconBitmap = favoriteIconDefaultBitmap;
+        }
+
+        // Apply the app settings from the shared preferences.
+        applyAppSettings();
+
+        // Instantiate the block list helper.
+        BlockListHelper blockListHelper = new BlockListHelper();
+
+        // Parse the block lists.
+        final ArrayList<List<String[]>> easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
+        final ArrayList<List<String[]>> easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
+        final ArrayList<List<String[]>> fanboyAnnoyance = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
+        final ArrayList<List<String[]>> fanboySocial = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
+
+        // Store the list versions.
+        easyListVersion = easyList.get(0).get(0)[0];
+        easyPrivacyVersion = easyPrivacy.get(0).get(0)[0];
+        fanboyAnnoyanceVersion = fanboyAnnoyance.get(0).get(0)[0];
+        fanboySocialVersion = fanboySocial.get(0).get(0)[0];
+
         mainWebView.setWebViewClient(new WebViewClient() {
             // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
             // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
             @SuppressWarnings("deprecation")
             @Override
             public boolean shouldOverrideUrlLoading(WebView view, String url) {
-                if (url.startsWith("mailto:")) {  // Load the URL in an external email program because it begins with `mailto:`.
-                    // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                if (url.startsWith("mailto:")) {  // Load the email address in an external email program.
+                    // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
 
                     // Parse the url and set it as the data for the `Intent`.
@@ -681,6 +1050,21 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     // Make it so.
                     startActivity(emailIntent);
 
+                    // Returning `true` indicates the application is handling the URL.
+                    return true;
+                } else if (url.startsWith("tel:")) {  // Load the phone number in the dialer.
+                    // `ACTION_DIAL` open the dialer and loads the phone number, but waits for the user to place the call.
+                    Intent dialIntent = new Intent(Intent.ACTION_DIAL);
+
+                    // Add the phone number to the intent.
+                    dialIntent.setData(Uri.parse(url));
+
+                    // `FLAG_ACTIVITY_NEW_TASK` opens the dialer in a new task instead as part of Privacy Browser.
+                    dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                    // Make it so.
+                    startActivity(dialIntent);
+
                     // Returning `true` indicates the application is handling the URL.
                     return true;
                 } else {  // Load the URL in Privacy Browser.
@@ -692,46 +1076,67 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 }
             }
 
-            // Block ads.  We have to use the deprecated `shouldInterceptRequest` until minimum API >= 21.
+            // Check requests against the block lists.  The deprecated `shouldInterceptRequest` must be used until minimum API >= 21.
             @SuppressWarnings("deprecation")
             @Override
             public WebResourceResponse shouldInterceptRequest(WebView view, String url){
-                if (adBlockerEnabled) {  // Block ads.
-                    // Extract the host from `url`.
-                    Uri requestUri = Uri.parse(url);
-                    String requestHost = requestUri.getHost();
-
-                    // Initialize a variable to track if this is an ad server.
-                    boolean requestHostIsAdServer = false;
-
-                    // Check all the subdomains of `requestHost` if it is not `null` against the ad server database.
-                    if (requestHost != null) {
-                        while (requestHost.contains(".") && !requestHostIsAdServer) {  // Stop checking if we run out of `.` or if we already know that `requestHostIsAdServer` is `true`.
-                            if (adServersSet.contains(requestHost)) {
-                                requestHostIsAdServer = true;
-                            }
+                // Create an empty web resource response to be used if the resource request is blocked.
+                WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
+
+                // Check EasyList if it is enabled.
+                if (easyListEnabled) {
+                    if (blockListHelper.isBlocked(formattedUrlString, url, easyList)) {
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    }
+                }
 
-                            // Strip out the lowest subdomain of `requestHost`.
-                            requestHost = requestHost.substring(requestHost.indexOf(".") + 1);
-                        }
+                // Check EasyPrivacy if it is enabled.
+                if (easyPrivacyEnabled) {
+                    if (blockListHelper.isBlocked(formattedUrlString, url, easyPrivacy)) {
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
                     }
+                }
 
-                    if (requestHostIsAdServer) {  // It is an ad server.
-                        // Return an empty `WebResourceResponse`.
-                        return new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
-                    } else {  // It is not an ad server.
-                        // `return null` loads the requested resource.
-                        return null;
+                // Check Fanboy’s Annoyance List if it is enabled.
+                if (fanboyAnnoyanceListEnabled) {
+                    if (blockListHelper.isBlocked(formattedUrlString, url, fanboyAnnoyance)) {
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
+                    }
+                } else if (fanboySocialBlockingListEnabled){  // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
+                    if (blockListHelper.isBlocked(formattedUrlString, url, fanboySocial)) {
+                        // The resource request was blocked.  Return an empty web resource response.
+                        return emptyWebResourceResponse;
                     }
-                } else {  // Ad blocking is disabled.
-                    // `return null` loads the requested resource.
-                    return null;
                 }
+
+                // The resource request has not been blocked.  `return null` loads the requested resource.
+                return null;
+            }
+
+            // Handle HTTP authentication requests.
+            @Override
+            public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
+                // Store `handler` so it can be accessed from `onHttpAuthenticationCancel()` and `onHttpAuthenticationProceed()`.
+                httpAuthHandler = handler;
+
+                // Display the HTTP authentication dialog.
+                AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
+                httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
             }
 
             // Update the URL in urlTextBox when the page starts to load.
             @Override
-            public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            public void onPageStarted(WebView view, String url, Bitmap favicon) {// If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
+                if (nightMode) {
+                    mainWebView.setVisibility(View.INVISIBLE);
+                }
+
+                // Hide the keyboard.  `0` indicates no additional flags.
+                inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+
                 // Check to see if we are waiting on Orbot.
                 if (!waitingForOrbot) {  // We are not waiting on Orbot, so we need to process the URL.
                     // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
@@ -753,9 +1158,14 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 }
             }
 
-            // Update formattedUrlString and urlTextBox.  It is necessary to do this after the page finishes loading because the final URL can change during load.
+            // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
             @Override
             public void onPageFinished(WebView view, String url) {
+                // Flush any cookies to persistent storage.  `CookieManager` has become very lazy about flushing cookies in recent versions.
+                if (Build.VERSION.SDK_INT >= 21) {
+                    cookieManager.flush();
+                }
+
                 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
                 urlIsLoading = false;
 
@@ -851,232 +1261,67 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
                         if (currentWebsiteSslEndDate != null) {
                             currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
-                        }
-
-                        if (pinnedDomainSslStartDate != null) {
-                            pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
-                        }
-
-                        if (pinnedDomainSslEndDate != null) {
-                            pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
-                        }
-
-                        // Check to see if the pinned SSL certificate matches the current website certificate.
-                        if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) || !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) ||
-                                !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) || !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
-                                !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {  // The pinned SSL certificate doesn't match the current domain certificate.
-                            //Display the pinned SSL certificate mismatch `AlertDialog`.
-                            AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
-                            pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_mismatch));
-                        }
-                    }
-                }
-            }
-
-            // Handle SSL Certificate errors.
-            @Override
-            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-                // Get the current website SSL certificate.
-                SslCertificate currentWebsiteSslCertificate = error.getCertificate();
-
-                // Extract the individual pieces of information from the current website SSL certificate.
-                String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
-                String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
-                String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
-                String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
-                String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
-                String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
-                Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
-                Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
-
-                // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
-                if (pinnedDomainSslCertificate &&
-                        currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) && currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) &&
-                        currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) && currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
-                        currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {  // An SSL certificate is pinned and matches the current domain certificate.
-                    // Proceed to the website without displaying an error.
-                    handler.proceed();
-                } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
-                    // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
-                    sslErrorHandler = handler;
-
-                    // Display the SSL error `AlertDialog`.
-                    AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
-                    sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
-                }
-            }
-        });
-
-        // Get a handle for the progress bar.
-        final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
-
-        mainWebView.setWebChromeClient(new WebChromeClient() {
-            // Update the progress bar when a page is loading.
-            @Override
-            public void onProgressChanged(WebView view, int progress) {
-                progressBar.setProgress(progress);
-                if (progress < 100) {
-                    progressBar.setVisibility(View.VISIBLE);
-                } else {
-                    progressBar.setVisibility(View.GONE);
-
-                    //Stop the `SwipeToRefresh` indicator if it is running
-                    swipeRefreshLayout.setRefreshing(false);
-                }
-            }
-
-            // Set the favorite icon when it changes.
-            @Override
-            public void onReceivedIcon(WebView view, Bitmap icon) {
-                // Only update the favorite icon if the website has finished loading.
-                if (progressBar.getVisibility() == View.GONE) {
-                    // Save a copy of the favorite icon.
-                    favoriteIconBitmap = icon;
-
-                    // Place the favorite icon in the appBar.
-                    favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
-                }
-            }
-
-            // Save a copy of the title when it changes.
-            @Override
-            public void onReceivedTitle(WebView view, String title) {
-                // Save a copy of the title.
-                webViewTitle = title;
-            }
-
-            // Enter full screen video
-            @Override
-            public void onShowCustomView(View view, CustomViewCallback callback) {
-                // Pause the ad if this is the free flavor.
-                if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    BannerAd.pauseAd(adView);
-                }
-
-                // Remove the translucent overlays.
-                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
-                // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
-                drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-
-                /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
-                 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
-                 */
-                rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
-                // Set `rootCoordinatorLayout` to fill the entire screen.
-                rootCoordinatorLayout.setFitsSystemWindows(false);
-
-                // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
-                fullScreenVideoFrameLayout.addView(view);
-                fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
-            }
-
-            // Exit full screen video
-            public void onHideCustomView() {
-                // Hide `fullScreenVideoFrameLayout`.
-                fullScreenVideoFrameLayout.removeAllViews();
-                fullScreenVideoFrameLayout.setVisibility(View.GONE);
-
-                // Add the translucent status flag.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
-                getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
-                // Set `rootCoordinatorLayout` to fit inside the status and navigation bars.  This also clears the `SYSTEM_UI` flags.
-                rootCoordinatorLayout.setFitsSystemWindows(true);
-
-                // Show the ad if this is the free flavor.
-                if (BuildConfig.FLAVOR.contentEquals("free")) {
-                    // Reload the ad.  Because the screen may have rotated, we need to use `reloadAfterRotate`.
-                    BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
-
-                    // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
-                    adView = findViewById(R.id.adview);
-                }
-            }
-        });
-
-        // Register `mainWebView` for a context menu.  This is used to see link targets and download images.
-        registerForContextMenu(mainWebView);
-
-        // Allow the downloading of files.
-        mainWebView.setDownloadListener(new DownloadListener() {
-            @Override
-            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
-                // Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`.
-                AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
-                downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
-            }
-        });
-
-        // Allow pinch to zoom.
-        mainWebView.getSettings().setBuiltInZoomControls(true);
-
-        // Hide zoom controls.
-        mainWebView.getSettings().setDisplayZoomControls(false);
-
-        // Set `mainWebView` to use a wide viewport.  Otherwise, some web pages will be scrunched and some content will render outside the screen.
-        mainWebView.getSettings().setUseWideViewPort(true);
-
-        // Set `mainWebView` to load in overview mode (zoomed out to the maximum width).
-        mainWebView.getSettings().setLoadWithOverviewMode(true);
-
-        // Initialize cookieManager.
-        cookieManager = CookieManager.getInstance();
-
-        // 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).
-        customHeaders.put("X-Requested-With", "");
-
-        // Initialize the default preference values the first time the program is run.  `this` is the context.  `false` keeps this command from resetting any current preferences back to default.
-        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
-
-        // Get the intent that started the app.
-        final Intent launchingIntent = getIntent();
-
-        // Extract the launching intent data as `launchingIntentUriData`.
-        final Uri launchingIntentUriData = launchingIntent.getData();
-
-        // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`.
-        if (launchingIntentUriData != null) {
-            formattedUrlString = launchingIntentUriData.toString();
-        }
-
-        // Get a handle for the `Runtime`.
-        privacyBrowserRuntime = Runtime.getRuntime();
-
-        // Store the application's private data directory.
-        privateDataDirectoryString = getApplicationInfo().dataDir;  // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+                        }
 
-        // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
-        inFullScreenBrowsingMode = false;
+                        if (pinnedDomainSslStartDate != null) {
+                            pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
+                        }
 
-        // Initialize AdView for the free flavor.
-        adView = findViewById(R.id.adview);
+                        if (pinnedDomainSslEndDate != null) {
+                            pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
+                        }
 
-        // Initialize the privacy settings variables.
-        javaScriptEnabled = false;
-        firstPartyCookiesEnabled = false;
-        thirdPartyCookiesEnabled = false;
-        domStorageEnabled = false;
-        saveFormDataEnabled = false;
+                        // Check to see if the pinned SSL certificate matches the current website certificate.
+                        if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) ||
+                                !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) ||
+                                !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
+                                !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {
+                            // The pinned SSL certificate doesn't match the current domain certificate.
+                            //Display the pinned SSL certificate mismatch `AlertDialog`.
+                            AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
+                            pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
+                        }
+                    }
+                }
+            }
 
-        // Initialize `webViewTitle`.
-        webViewTitle = getString(R.string.no_title);
+            // Handle SSL Certificate errors.
+            @Override
+            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+                // Get the current website SSL certificate.
+                SslCertificate currentWebsiteSslCertificate = error.getCertificate();
 
-        // Initialize `favoriteIconBitmap`.  We have to use `ContextCompat` until API >= 21.
-        Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
-        BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
-        favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
+                // Extract the individual pieces of information from the current website SSL certificate.
+                String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
+                String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
+                String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
+                String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
+                String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
+                String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
+                Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
+                Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
 
-        // If the favorite icon is null, load the default.
-        if (favoriteIconBitmap == null) {
-            favoriteIconBitmap = favoriteIconDefaultBitmap;
-        }
+                // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
+                if (pinnedDomainSslCertificate &&
+                        currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) &&
+                        currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) &&
+                        currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
+                        currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {
+                    // An SSL certificate is pinned and matches the current domain certificate.
+                    // Proceed to the website without displaying an error.
+                    handler.proceed();
+                } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
+                    // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
+                    sslErrorHandler = handler;
 
-        // Apply the app settings from the shared preferences.
-        applyAppSettings();
+                    // Display the SSL error `AlertDialog`.
+                    AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
+                    sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+                }
+            }
+        });
 
-        // Load `formattedUrlString` if we are not waiting for Orbot to connect.
+        // Load the website if not waiting for Orbot to connect.
         if (!waitingForOrbot) {
             loadUrl(formattedUrlString);
         }
@@ -1107,11 +1352,21 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
     @Override
     public void onRestart() {
+        // Run the default commands.
         super.onRestart();
 
         // Apply the app settings, which may have been changed in `SettingsActivity`.
         applyAppSettings();
 
+        // Apply the domain settings if returning from the Domains Activity.
+        if (reapplyDomainSettingsOnRestart) {
+            // Reset `reapplyDomainSettingsOnRestart`.
+            reapplyDomainSettingsOnRestart = false;
+
+            // Reapply the domain settings.
+            applyDomainSettings(formattedUrlString);
+        }
+
         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
         updatePrivacyIcons(true);
 
@@ -1119,18 +1374,40 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         setDisplayWebpageImages();
 
         // Reload the webpage if displaying of images has been disabled in `SettingsFragment`.
-        if (reloadOnRestartBoolean) {
+        if (reloadOnRestart) {
             // Reload `mainWebView`.
             mainWebView.reload();
 
             // Reset `reloadOnRestartBoolean`.
-            reloadOnRestartBoolean = false;
+            reloadOnRestart = false;
+        }
+
+        // Load the URL on restart to apply changes to night mode.
+        if (loadUrlOnRestart) {
+            // Load the current `formattedUrlString`.
+            loadUrl(formattedUrlString);
+
+            // Reset `loadUrlOnRestart.
+            loadUrlOnRestart = false;
+        }
+
+        //
+        if (restartFromBookmarksActivity) {
+            // Close the bookmarks drawer.
+            drawerLayout.closeDrawer(GravityCompat.END);
+
+            // Reload the bookmarks drawer.
+            loadBookmarksFolder();
+
+            // Reset `restartFromBookmarksActivity`.
+            restartFromBookmarksActivity = false;
         }
     }
 
     // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
     @Override
     public void onResume() {
+        // Run the default commands.
         super.onResume();
 
         // Resume JavaScript (if enabled).
@@ -1201,10 +1478,12 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         // Get handles for the menu items.
+        MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
         MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
         MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
         MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);
+        MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
         MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
         MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);
@@ -1212,6 +1491,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
         MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
 
+        // Set the text for the domain menu item.
+        if (domainSettingsApplied) {
+            addOrEditDomain.setTitle(R.string.edit_domain_settings);
+        } else {
+            addOrEditDomain.setTitle(R.string.add_domain_settings);
+        }
+
         // Set the status of the menu item checkboxes.
         toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
         toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
@@ -1249,6 +1535,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
         clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
 
+        // Enable `Clear Data` if any of the submenu items are enabled.
+        clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
+
         // Initialize font size variables.
         int fontSize = mainWebView.getSettings().getTextZoom();
         String fontSizeTitle;
@@ -1257,47 +1546,47 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // Prepare the font size title and current size menu item.
         switch (fontSize) {
             case 25:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.twenty_five_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
                 break;
 
             case 50:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
                 break;
 
             case 75:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
                 break;
 
             case 100:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
                 break;
 
             case 125:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
                 break;
 
             case 150:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
                 break;
 
             case 175:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
                 break;
 
             case 200:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
                 break;
 
             default:
-                fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
+                fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
                 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
                 break;
         }
@@ -1312,7 +1601,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // Run all the other default commands.
         super.onPrepareOptionsMenu(menu);
 
-        // `return true` displays the menu.
+        // Display the menu.
         return true;
     }
 
@@ -1322,10 +1611,32 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // removeAllCookies is deprecated, but it is required for API < 21.
     @SuppressWarnings("deprecation")
     public boolean onOptionsItemSelected(MenuItem menuItem) {
+        // Get the selected menu item ID.
         int menuItemId = menuItem.getItemId();
 
         // Set the commands that relate to the menu entries.
         switch (menuItemId) {
+            case R.id.add_or_edit_domain:
+                if (domainSettingsApplied) {  // Edit the current domain settings.
+                    // Reapply the domain settings on returning to `MainWebViewActivity`.
+                    reapplyDomainSettingsOnRestart = true;
+                    currentDomainName = "";
+
+                    // Create an intent to launch the domains activity.
+                    Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+                    // Put extra information instructing the domains activity to directly load the current domain.
+                    domainsIntent.putExtra("LoadDomain", domainSettingsDatabaseId);
+
+                    // Make it so.
+                    startActivity(domainsIntent);
+                } else {  // Add a new domain.
+                    // Show the add domain `AlertDialog`.
+                    AppCompatDialogFragment addDomainDialog = new AddDomainDialog();
+                    addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain));
+                }
+                return true;
+
             case R.id.toggle_javascript:
                 // Switch the status of javaScriptEnabled.
                 javaScriptEnabled = !javaScriptEnabled;
@@ -1448,11 +1759,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
             case R.id.clear_cookies:
                 Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, new View.OnClickListener() {
-                            @Override
-                            public void onClick(View v) {
-                                // Do nothing because everything will be handled by `onDismissed()` below.
-                            }
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
                         })
                         .addCallback(new Snackbar.Callback() {
                             @Override
@@ -1480,11 +1788,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
             case R.id.clear_dom_storage:
                 Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, new View.OnClickListener() {
-                            @Override
-                            public void onClick(View v) {
-                                // Do nothing because everything will be handled by `onDismissed()` below.
-                            }
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
                         })
                         .addCallback(new Snackbar.Callback() {
                             @Override
@@ -1501,9 +1806,16 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                                         WebStorage webStorage = WebStorage.getInstance();
                                         webStorage.deleteAllData();
 
-                                        // Manually remove `IndexedDB` if it exists.
+                                        // Manually delete the DOM storage files and directories.
                                         try {
+                                            // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+                                            privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+                                            // Multiple commands must be used because `Runtime.exec()` does not like `*`.
                                             privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                                            privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+                                            privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+                                            privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
                                         } catch (IOException e) {
                                             // Do nothing if an error is thrown.
                                         }
@@ -1515,11 +1827,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
             case R.id.clear_form_data:
                 Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
-                        .setAction(R.string.undo, new View.OnClickListener() {
-                            @Override
-                            public void onClick(View v) {
-                                // Do nothing because everything will be handled by `onDismissed()` below.
-                            }
+                        .setAction(R.string.undo, v -> {
+                            // Do nothing because everything will be handled by `onDismissed()` below.
                         })
                         .addCallback(new Snackbar.Callback() {
                             @Override
@@ -1606,17 +1915,14 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 // Show the Find on Page `RelativeLayout`.
                 findOnPageLinearLayout.setVisibility(View.VISIBLE);
 
-                // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.  http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
-                findOnPageEditText.postDelayed(new Runnable() {
-                    @Override
-                    public void run()
-                    {
-                        // Set the focus on `findOnPageEditText`.
-                        findOnPageEditText.requestFocus();
+                // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.
+                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
+                findOnPageEditText.postDelayed(() -> {
+                    // Set the focus on `findOnPageEditText`.
+                    findOnPageEditText.requestFocus();
 
-                        // Display the keyboard.  `0` sets no input flags.
-                        inputMethodManager.showSoftInput(findOnPageEditText, 0);
-                    }
+                    // Display the keyboard.  `0` sets no input flags.
+                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
                 }, 200);
                 return true;
 
@@ -1627,14 +1933,23 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 // Convert `mainWebView` to `printDocumentAdapter`.
                 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
 
+                // Remove the lint error below that `printManager` might be `null`.
+                assert printManager != null;
+
                 // Print the document.  The print attributes are `null`.
-                printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+                return true;
+
+            case R.id.view_source:
+                // Launch the Vew Source activity.
+                Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
+                startActivity(viewSourceIntent);
                 return true;
 
             case R.id.add_to_homescreen:
                 // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
                 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
-                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut));
+                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
 
                 //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below.
                 return true;
@@ -1686,13 +2001,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
                 // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`.  `this` is the `Context`.
                 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
-                urlHistoryDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.history));
-                break;
-
-            case R.id.bookmarks:
-                // Launch BookmarksActivity.
-                Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
-                startActivity(bookmarksIntent);
+                urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
                 break;
 
             case R.id.downloads:
@@ -1705,17 +2014,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 startActivity(downloadManagerIntent);
                 break;
 
-            case R.id.settings:
-                // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`.
-                currentDomainName = "";
-
-                // Launch `SettingsActivity`.
-                Intent settingsIntent = new Intent(this, SettingsActivity.class);
-                startActivity(settingsIntent);
-                break;
-
             case R.id.domains:
-                // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`.
+                // Reapply the domain settings on returning to `MainWebViewActivity`.
+                reapplyDomainSettingsOnRestart = true;
                 currentDomainName = "";
 
                 // Launch `DomainsActivity`.
@@ -1723,6 +2024,16 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 startActivity(domainsIntent);
                 break;
 
+            case R.id.settings:
+                // Reapply the domain settings on returning to `MainWebViewActivity`.
+                reapplyDomainSettingsOnRestart = true;
+                currentDomainName = "";
+
+                // Launch `SettingsActivity`.
+                Intent settingsIntent = new Intent(this, SettingsActivity.class);
+                startActivity(settingsIntent);
+                break;
+
             case R.id.guide:
                 // Launch `GuideActivity`.
                 Intent guideIntent = new Intent(this, GuideActivity.class);
@@ -1768,10 +2079,10 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
                     // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
                     try {
-                        // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+                        // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
                         privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
 
-                        // We have to use multiple commands because `Runtime.exec()` does not like `*`.
+                        // Multiple commands must be used because `Runtime.exec()` does not like `*`.
                         privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
                         privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
                         privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
@@ -1805,8 +2116,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     try {
                         // Delete the main cache directory.
                         privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
-
-                        // Delete the secondary `Service Worker` cache directory.  We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+                        // Delete the secondary `Service Worker` cache directory.
+                        // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
                         privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
                     } catch (IOException e) {
                         // Do nothing if an error is thrown.
@@ -1879,7 +2190,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             adView = findViewById(R.id.adview);
         }
 
-        // `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:  https://code.google.com/p/android/issues/detail?id=20493#c8
+        // `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:
+        // https://code.google.com/p/android/issues/detail?id=20493#c8
         // ActivityCompat.invalidateOptionsMenu(this);
     }
 
@@ -1895,6 +2207,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // Get a handle for the `ClipboardManager`.
         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
 
+        // Remove the lint errors below that `clipboardManager` might be `null`.
+        assert clipboardManager != null;
+
         switch (hitTestResult.getType()) {
             // `SRC_ANCHOR_TYPE` is a link.
             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
@@ -1905,25 +2220,19 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 menu.setHeaderTitle(linkUrl);
 
                 // Add a `Load URL` entry.
-                menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        loadUrl(linkUrl);
-                        return false;
-                    }
+                menu.add(R.string.load_url).setOnMenuItemClickListener(item -> {
+                    loadUrl(linkUrl);
+                    return false;
                 });
 
                 // Add a `Copy URL` entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        // Save the link URL in a `ClipData`.
-                        ClipData srcAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), linkUrl);
-
-                        // Set the `ClipData` as the clipboard's primary clip.
-                        clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
-                        return false;
-                    }
+                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+                    // Save the link URL in a `ClipData`.
+                    ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
+
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
+                    return false;
                 });
 
                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
@@ -1938,35 +2247,29 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 menu.setHeaderTitle(linkUrl);
 
                 // Add a `Write Email` entry.
-                menu.add(R.string.write_email).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
-                        Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+                menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
+                    // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+                    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
 
-                        // Parse the url and set it as the data for the `Intent`.
-                        emailIntent.setData(Uri.parse("mailto:" + linkUrl));
+                    // Parse the url and set it as the data for the `Intent`.
+                    emailIntent.setData(Uri.parse("mailto:" + linkUrl));
 
-                        // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
-                        emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
+                    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-                        // Make it so.
-                        startActivity(emailIntent);
-                        return false;
-                    }
+                    // Make it so.
+                    startActivity(emailIntent);
+                    return false;
                 });
 
                 // Add a `Copy Email Address` entry.
-                menu.add(R.string.copy_email_address).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        // Save the email address in a `ClipData`.
-                        ClipData srcEmailTypeClipData = ClipData.newPlainText(getResources().getString(R.string.email_address), linkUrl);
-
-                        // Set the `ClipData` as the clipboard's primary clip.
-                        clipboardManager.setPrimaryClip(srcEmailTypeClipData);
-                        return false;
-                    }
+                menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
+                    // Save the email address in a `ClipData`.
+                    ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
+
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcEmailTypeClipData);
+                    return false;
                 });
 
                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
@@ -1982,36 +2285,27 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 menu.setHeaderTitle(imageUrl);
 
                 // Add a `View Image` entry.
-                menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        loadUrl(imageUrl);
-                        return false;
-                    }
+                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+                    loadUrl(imageUrl);
+                    return false;
                 });
 
                 // Add a `Download Image` entry.
-                menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
-                        AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
-                        downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
-                        return false;
-                    }
+                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
+                    AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+                    downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                    return false;
                 });
 
                 // Add a `Copy URL` entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        // Save the image URL in a `ClipData`.
-                        ClipData srcImageTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl);
-
-                        // Set the `ClipData` as the clipboard's primary clip.
-                        clipboardManager.setPrimaryClip(srcImageTypeClipData);
-                        return false;
-                    }
+                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+                    // Save the image URL in a `ClipData`.
+                    ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcImageTypeClipData);
+                    return false;
                 });
 
                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
@@ -2028,36 +2322,27 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 menu.setHeaderTitle(imageUrl);
 
                 // Add a `View Image` entry.
-                menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        loadUrl(imageUrl);
-                        return false;
-                    }
+                menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+                    loadUrl(imageUrl);
+                    return false;
                 });
 
                 // Add a `Download Image` entry.
-                menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
-                        AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
-                        downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
-                        return false;
-                    }
+                menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+                    // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
+                    AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+                    downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+                    return false;
                 });
 
                 // Add a `Copy URL` entry.
-                menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-                    @Override
-                    public boolean onMenuItemClick(MenuItem item) {
-                        // Save the image URL in a `ClipData`.
-                        ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl);
-
-                        // Set the `ClipData` as the clipboard's primary clip.
-                        clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
-                        return false;
-                    }
+                menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+                    // Save the image URL in a `ClipData`.
+                    ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+
+                    // Set the `ClipData` as the clipboard's primary clip.
+                    clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
+                    return false;
                 });
 
                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
@@ -2066,23 +2351,133 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         }
     }
 
+    @Override
+    public void onAddDomain(AppCompatDialogFragment dialogFragment) {
+        // Reapply the domain settings on returning to `MainWebViewActivity`.
+        reapplyDomainSettingsOnRestart = true;
+        currentDomainName = "";
+
+        // Get the new domain name `String` from `dialogFragment`.
+        EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
+        String domainNameString = domainNameEditText.getText().toString();
+
+        // Initialize the database handler.  `this` specifies the context.  The two `nulls` do not specify the database name or a `CursorFactory`.
+        // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+        DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+
+        // Create the domain and store the database ID in `currentDomainDatabaseId`.
+        int newDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString);
+
+        // Create an intent to launch the domains activity.
+        Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+        // Put extra information instructing the domains activity to directly load the current domain.
+        domainsIntent.putExtra("LoadDomain", newDomainDatabaseId);
+
+        // Make it so.
+        startActivity(domainsIntent);
+    }
+
+    @Override
+    public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
+        // Get the `EditTexts` from the `dialogFragment`.
+        EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
+        EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
+
+        // Extract the strings from the `EditTexts`.
+        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
+        String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
+
+        // Convert the favoriteIcon Bitmap to a byte array.  `0` is for lossless compression (the only option for a PNG).
+        ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+        favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+        byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
+
+        // Display the new bookmark below the current items in the (0 indexed) list.
+        int newBookmarkDisplayOrder = bookmarksListView.getCount();
+
+        // Create the bookmark.
+        bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
+
+        // Update `bookmarksCursor` with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+
+        // Scroll to the new bookmark.
+        bookmarksListView.setSelection(newBookmarkDisplayOrder);
+    }
+
+    @Override
+    public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
+        // Get handles for the views in `dialogFragment`.
+        EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
+        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
+        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
+
+        // Get new folder name string.
+        String folderNameString = createFolderNameEditText.getText().toString();
+
+        // Get the new folder icon `Bitmap`.
+        Bitmap folderIconBitmap;
+        if (defaultFolderIconRadioButton.isChecked()) {  // Use the default folder icon.
+            // Get the default folder icon and convert it to a `Bitmap`.
+            Drawable folderIconDrawable = folderIconImageView.getDrawable();
+            BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+            folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+        } else {  // Use the `WebView` favorite icon.
+            folderIconBitmap = favoriteIconBitmap;
+        }
+
+        // Convert `folderIconBitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
+        ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
+        folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
+        byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+
+        // Move all the bookmarks down one in the display order.
+        for (int i = 0; i < bookmarksListView.getCount(); i++) {
+            int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
+            bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
+        }
+
+        // Create the folder, which will be placed at the top of the `ListView`.
+        bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
+
+        // Update `bookmarksCursor` with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+
+        // Scroll to the new folder.
+        bookmarksListView.setSelection(0);
+    }
+
     @Override
     public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
-        // Get shortcutNameEditText from the alert dialog.
-        EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
-
-        // Create the bookmark shortcut based on formattedUrlString.
-        Intent bookmarkShortcut = new Intent();
-        bookmarkShortcut.setAction(Intent.ACTION_VIEW);
-        bookmarkShortcut.setData(Uri.parse(formattedUrlString));
-
-        // Place the bookmark shortcut on the home screen.
-        Intent placeBookmarkShortcut = new Intent();
-        placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
-        placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
-        placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIconBitmap);
-        placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
-        sendBroadcast(placeBookmarkShortcut);
+        // Get the shortcut name.
+        EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
+        String shortcutNameString = shortcutNameEditText.getText().toString();
+
+        // Convert the favorite icon bitmap to an `Icon`.  `IconCompat` is required until API >= 26.
+        IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
+
+        // Setup the shortcut intent.
+        Intent shortcutIntent = new Intent();
+        shortcutIntent.setAction(Intent.ACTION_VIEW);
+        shortcutIntent.setData(Uri.parse(formattedUrlString));
+
+        // Create a shortcut info builder.  The shortcut name becomes the shortcut ID.
+        ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
+
+        // Add the required fields to the shortcut info builder.
+        shortcutInfoBuilder.setIcon(favoriteIcon);
+        shortcutInfoBuilder.setIntent(shortcutIntent);
+        shortcutInfoBuilder.setShortLabel(shortcutNameString);
+
+        // Request the pin.  `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
+        ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
     }
 
     @Override
@@ -2106,7 +2501,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             }
 
             // Get the file name from `dialogFragment`.
-            EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name);
+            EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
             String imageName = downloadImageNameEditText.getText().toString();
 
             // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
@@ -2125,6 +2520,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             // Show the download notification after the download is completed.
             downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 
+            // Remove the lint warning below that `downloadManager` might be `null`.
+            assert downloadManager != null;
+
             // Initiate the download.
             downloadManager.enqueue(downloadRequest);
         } else {  // The image is not an HTTP or HTTPS URI.
@@ -2154,7 +2552,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             }
 
             // Get the file name from `dialogFragment`.
-            EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
+            EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
             String fileName = downloadFileNameEditText.getText().toString();
 
             // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
@@ -2173,6 +2571,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             // Show the download notification after the download is completed.
             downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
 
+            // Remove the lint warning below that `downloadManager` might be `null`.
+            assert downloadManager != null;
+
             // Initiate the download.
             downloadManager.enqueue(downloadRequest);
         } else {  // The download is not an HTTP or HTTPS URI.
@@ -2180,10 +2581,119 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         }
     }
 
+    @Override
+    public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
+        // Get handles for the views from `dialogFragment`.
+        EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
+        EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
+        RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
+
+        // Store the bookmark strings.
+        String bookmarkNameString = editBookmarkNameEditText.getText().toString();
+        String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
+
+        // Update the bookmark.
+        if (currentBookmarkIconRadioButton.isChecked()) {  // Update the bookmark without changing the favorite icon.
+            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
+        } else {  // Update the bookmark using the `WebView` favorite icon.
+            // Convert the favorite icon to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+            favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
+            byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
+
+            //  Update the bookmark and the favorite icon.
+            bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
+        }
+
+        // Update `bookmarksCursor` with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+    }
+
+    @Override
+    public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
+        // Get handles for the views from `dialogFragment`.
+        EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
+        RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
+        RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
+        ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
+
+        // Get the new folder name.
+        String newFolderNameString = editFolderNameEditText.getText().toString();
+
+        // Check if the favorite icon has changed.
+        if (currentFolderIconRadioButton.isChecked()) {  // Only the name has changed.
+            // Update the name in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
+        } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) {  // Only the icon has changed.
+            // Get the new folder icon `Bitmap`.
+            Bitmap folderIconBitmap;
+            if (defaultFolderIconRadioButton.isChecked()) {
+                // Get the default folder icon and convert it to a `Bitmap`.
+                Drawable folderIconDrawable = folderIconImageView.getDrawable();
+                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+            } else {  // Use the `WebView` favorite icon.
+                folderIconBitmap = favoriteIconBitmap;
+            }
+
+            // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
+            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
+            byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+
+            // Update the folder icon in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
+        } else {  // The folder icon and the name have changed.
+            // Get the new folder icon `Bitmap`.
+            Bitmap folderIconBitmap;
+            if (defaultFolderIconRadioButton.isChecked()) {
+                // Get the default folder icon and convert it to a `Bitmap`.
+                Drawable folderIconDrawable = folderIconImageView.getDrawable();
+                BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+                folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+            } else {  // Use the `WebView` favorite icon.
+                folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
+            }
+
+            // Convert the folder `Bitmap` to a byte array.  `0` is for lossless compression (the only option for a PNG).
+            ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
+            folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
+            byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
+
+            // Update the folder name and icon in the database.
+            bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
+        }
+
+        // Update `bookmarksCursor` with the current contents of this folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+
+        // Update the `ListView`.
+        bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+    }
+
+    @Override
+    public void onHttpAuthenticationCancel() {
+        // Cancel the `HttpAuthHandler`.
+        httpAuthHandler.cancel();
+    }
+
+    @Override
+    public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
+        // Get handles for the `EditTexts`.
+        EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
+        EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
+
+        // Proceed with the HTTP authentication.
+        httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
+    }
+
     public void viewSslCertificate(View view) {
         // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
         DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
-        viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
+        viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate));
     }
 
     @Override
@@ -2234,21 +2744,30 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
     @Override
     public void onBackPressed() {
-        // Close the navigation drawer if it is available.  GravityCompat.START is the drawer on the left on Left-to-Right layout text.
-        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
+        if (drawerLayout.isDrawerVisible(GravityCompat.START)) {  // The navigation drawer is open.
+            // Close the navigation drawer.
             drawerLayout.closeDrawer(GravityCompat.START);
-        } else {
-            // Load the previous URL if available.
-            if (mainWebView.canGoBack()) {
-                // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
-                navigatingHistory = true;
-
-                // Go back.
-                mainWebView.goBack();
-            } else {
-                // Pass `onBackPressed()` to the system.
-                super.onBackPressed();
+        } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){  // The bookmarks drawer is open.
+            if (currentBookmarksFolder.isEmpty()) {  // The home folder is displayed.
+                // close the bookmarks drawer.
+                drawerLayout.closeDrawer(GravityCompat.END);
+            } else {  // A subfolder is displayed.
+                // Place the former parent folder in `currentFolder`.
+                currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolder(currentBookmarksFolder);
+
+                // Load the new folder.
+                loadBookmarksFolder();
             }
+
+        } else if (mainWebView.canGoBack()) {  // There is at least one item in the `WebView` history.
+            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
+            navigatingHistory = true;
+
+            // Go back.
+            mainWebView.goBack();
+        } else {  // There isn't anything to do in Privacy Browser.
+            // Pass `onBackPressed()` to the system.
+            super.onBackPressed();
         }
     }
 
@@ -2294,10 +2813,10 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             formattedUrlString = searchURL + encodedUrlString;
         }
 
-        loadUrl(formattedUrlString);
+        // Clear the focus from the URL text box.  Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
+        urlTextBox.clearFocus();
 
-        // Hide the keyboard so we can see the webpage.  `0` indicates no additional flags.
-        inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+        loadUrl(formattedUrlString);
     }
 
 
@@ -2340,17 +2859,20 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     }
 
     private void applyAppSettings() {
-        // Get a handle for `sharedPreferences`.  `this` references the current context.
+        // Get a handle for the shared preferences.
         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-        // Store the values from `sharedPreferences` in variables.
+        // Store the values from the shared preferences in variables.
         String homepageString = sharedPreferences.getString("homepage", "https://start.duckduckgo.com");
         String torHomepageString = sharedPreferences.getString("tor_homepage", "https://3g2upl4pq6kufc4m.onion");
         String torSearchString = sharedPreferences.getString("tor_search", "https://3g2upl4pq6kufc4m.onion/html/?q=");
         String torSearchCustomURLString = sharedPreferences.getString("tor_search_custom_url", "");
         String searchString = sharedPreferences.getString("search", "https://duckduckgo.com/html/?q=");
         String searchCustomURLString = sharedPreferences.getString("search_custom_url", "");
-        adBlockerEnabled = sharedPreferences.getBoolean("block_ads", true);
+        easyListEnabled = sharedPreferences.getBoolean("easylist", true);
+        easyPrivacyEnabled = sharedPreferences.getBoolean("easyprivacy", true);
+        fanboyAnnoyanceListEnabled = sharedPreferences.getBoolean("fanboy_annoyance_list", true);
+        fanboySocialBlockingListEnabled = sharedPreferences.getBoolean("fanboy_social_blocking_list", true);
         incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
         boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
         boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
@@ -2449,7 +2971,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
                 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
-                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
+                 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
                  */
                 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
             } else {  // Hide everything except the status and navigation bars.
@@ -2520,7 +3042,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             loadingNewDomainName = !hostName.equals(currentDomainName);
         }
 
-        // Only apply the domain settings if we are loading a new domain.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
+        // Only apply the domain settings if a new domain is being loaded.  This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
         if (loadingNewDomainName) {
             // Set the new `hostname` as the `currentDomainName`.
             currentDomainName = hostName;
@@ -2583,10 +3105,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             // Get a handle for the shared preference.  `this` references the current context.
             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-            // Store the default font size and user agent information.
+            // Store the general preference information.
             String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
             String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
             String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
+            nightMode = sharedPreferences.getBoolean("night_mode", false);
 
             if (domainSettingsApplied) {  // The url we are loading has custom domain settings.
                 // Get a cursor for the current host and move it to the first position.
@@ -2603,6 +3126,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
                 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
                 displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+                int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
                 pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
                 pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
                 pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
@@ -2611,6 +3135,22 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
                 pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
 
+                // Set `nightMode` according to `nightModeInt`.  If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
+                switch (nightModeInt) {
+                    case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
+                        nightMode = true;
+                        break;
+
+                    case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
+                        nightMode = false;
+                        break;
+                }
+
+                // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
+                if (nightMode) {
+                    javaScriptEnabled = true;
+                }
+
                 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
                 if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
                     pinnedDomainSslStartDate = null;
@@ -2646,7 +3186,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
                 }
 
-                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.  <https://redmine.stoutner.com/issues/160>
+                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
+                // <https://redmine.stoutner.com/issues/160>
                 if (!urlIsLoading) {
                     switch (userAgentString) {
                         case "System default user agent":
@@ -2677,6 +3218,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                             // Use the selected user agent.
                             mainWebView.getSettings().setUserAgentString(userAgentString);
                     }
+
+                    // Store the applied user agent string.
+                    appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
                 }
 
                 // Set a green background on `urlTextBox` to indicate that custom domain settings are being used.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
@@ -2693,6 +3237,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
                 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
 
+                // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
+                if (nightMode) {
+                    javaScriptEnabled = true;
+                }
+
                 // Apply the default settings.
                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
@@ -2717,7 +3266,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
                 }
 
-                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.  <https://redmine.stoutner.com/issues/160>
+                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.
+                // <https://redmine.stoutner.com/issues/160>
                 if (!urlIsLoading) {
                     switch (defaultUserAgentString) {
                         case "WebView default user agent":
@@ -2734,6 +3284,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                             // Use the selected user agent.
                             mainWebView.getSettings().setUserAgentString(defaultUserAgentString);
                     }
+
+                    // Store the applied user agent string.
+                    appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
                 }
 
                 // Set a transparent background on `urlTextBox`.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
@@ -2831,18 +3384,18 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             }
         }
 
-        // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.  `this` references the current activity.
+        // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
         if (runInvalidateOptionsMenu) {
-            ActivityCompat.invalidateOptionsMenu(this);
+            invalidateOptionsMenu();
         }
     }
 
     private void highlightUrlText() {
         String urlString = urlTextBox.getText().toString();
 
-        if (urlString.startsWith("http://")) {  // Highlight connections that are not encrypted.
+        if (urlString.startsWith("http://")) {  // Highlight the protocol of connections that are not encrypted.
             urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-        } else if (urlString.startsWith("https://")) {  // Highlight connections that are encrypted.
+        } else if (urlString.startsWith("https://")) {  // De-emphasize the protocol of connections that are encrypted.
             urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
         }
 
@@ -2854,4 +3407,55 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
         }
     }
+
+    private void loadBookmarksFolder() {
+        // Update `bookmarksCursor` with the contents of the bookmarks database for the current folder.
+        bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+
+        // Populate the bookmarks cursor adapter.  `this` specifies the `Context`.  `false` disables `autoRequery`.
+        bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
+            @Override
+            public View newView(Context context, Cursor cursor, ViewGroup parent) {
+                // Inflate the individual item layout.  `false` does not attach it to the root.
+                return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
+            }
+
+            @Override
+            public void bindView(View view, Context context, Cursor cursor) {
+                // Get handles for the views.
+                ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
+                TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
+
+                // Get the favorite icon byte array from the `Cursor`.
+                byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
+
+                // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
+                Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
+
+                // Display the bitmap in `bookmarkFavoriteIcon`.
+                bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
+
+                // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
+                String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+                bookmarkNameTextView.setText(bookmarkNameString);
+
+                // Make the font bold for folders.
+                if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
+                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
+                } else {  // Reset the font to default for normal bookmarks.
+                    bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
+                }
+            }
+        };
+
+        // Populate the `ListView` with the adapter.
+        bookmarksListView.setAdapter(bookmarksCursorAdapter);
+
+        // Set the bookmarks drawer title.
+        if (currentBookmarksFolder.isEmpty()) {
+            bookmarksTitleTextView.setText(R.string.bookmarks);
+        } else {
+            bookmarksTitleTextView.setText(currentBookmarksFolder);
+        }
+    }
 }