Add support for `tel:` links. https://redmine.stoutner.com/issues/226
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index f37f1241d2b85a5e36edf34799bb0ae9287329ce..29332ebc599c6511fce76c686fd8c1c9af9c599a 100644 (file)
@@ -34,6 +34,7 @@ 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;
@@ -42,11 +43,13 @@ 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;
@@ -71,10 +74,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;
@@ -83,21 +87,31 @@ 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.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.BookmarksDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
@@ -105,6 +119,8 @@ 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;
@@ -112,29 +128,32 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 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,
-        SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener {
+public class MainWebViewActivity extends AppCompatActivity implements 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`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`,
-    // `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
+    // `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`, `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 `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()`.
@@ -146,27 +165,48 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `displayWebpageImagesBoolean` is public static so it can be accessed from `DomainSettingsFragment`.  It is also used in `applyAppSettings()` and `applyDomainSettings()`.
     public static boolean displayWebpageImagesBoolean;
 
-    // `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;
+
+    // `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;
+
+    // `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;
+
+    // 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 int domainSettingsDatabaseId;
+    public static String pinnedDomainSslIssuedToCNameString;
+    public static String pinnedDomainSslIssuedToONameString;
+    public static String pinnedDomainSslIssuedToUNameString;
+    public static String pinnedDomainSslIssuedByCNameString;
+    public static String pinnedDomainSslIssuedByONameString;
+    public static String pinnedDomainSslIssuedByUNameString;
+    public static Date pinnedDomainSslStartDate;
+    public static Date pinnedDomainSslEndDate;
 
 
     // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applyAppSettings()`.
     private ActionBar appBar;
 
-    // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`.
+    // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
     private boolean navigatingHistory;
 
     // `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()`
-    // and `setDisplayWebpageImages()`.
+    // `onSslMismatchBack()`, and `setDisplayWebpageImages()`.
     private WebView mainWebView;
 
     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
@@ -187,22 +227,24 @@ 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;
 
@@ -215,7 +257,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `adBlockerEnabled` is used in `onCreate()` and `applyAppSettings()`.
     private boolean adBlockerEnabled;
 
-    // `privacyBrowserRuntime` is used in `onCreate()` and `applyAppSettings()`.
+    // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
     private Runtime privacyBrowserRuntime;
 
     // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
@@ -233,9 +275,12 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
     private boolean translucentNavigationBarOnFullscreen;
 
-    // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`.
+    // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
     private String currentDomainName;
 
+    // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
+    private boolean ignorePinnedSslCertificate;
+
     // `waitingForOrbot` is used in `onCreate()` and `applyAppSettings()`.
     private boolean waitingForOrbot;
 
@@ -251,7 +296,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `waitingForOrbotData` is used in `onCreate()` and `applyAppSettings()`.
     private String waitingForOrbotHTMLString;
 
-    // `privateDataDirectoryString` is used in `onCreate()` and `onNavigationItemSelected()`.
+    // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
     private String privateDataDirectoryString;
 
     // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
@@ -287,6 +332,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;
 
@@ -296,9 +344,30 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `urlIsLoading` is used in `onCreate()`, `loadUrl()`, and `applyDomainSettings()`.
     private boolean urlIsLoading;
 
+    // `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) {
@@ -324,8 +393,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();
 
@@ -342,42 +411,36 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
 
         // Get a handle for `urlTextBox`.
-        urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.url_edittext);
+        urlTextBox = appBar.getCustomView().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((v, 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;
+        urlTextBox.setOnKeyListener((v, keyCode, 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;
             }
         });
 
@@ -411,15 +474,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() {
@@ -499,12 +606,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`.
@@ -521,8 +628,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);
                 }
             }
         });
@@ -547,37 +657,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.
@@ -586,6 +689,67 @@ 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
@@ -648,8 +812,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             @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`.
@@ -661,6 +825,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.
@@ -709,11 +888,27 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 }
             }
 
+            // 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) {
-                // Reset `webViewTitle`
-                webViewTitle = getString(R.string.no_title);
+                // 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.
@@ -736,7 +931,7 @@ 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) {
                 // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
@@ -793,36 +988,149 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                         }
                     }
 
-                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog`.
+                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
                     sslCertificate = mainWebView.getCertificate();
+
+                    // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session.
+                    if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate) {
+                        // Initialize the current SSL certificate variables.
+                        String currentWebsiteIssuedToCName = "";
+                        String currentWebsiteIssuedToOName = "";
+                        String currentWebsiteIssuedToUName = "";
+                        String currentWebsiteIssuedByCName = "";
+                        String currentWebsiteIssuedByOName = "";
+                        String currentWebsiteIssuedByUName = "";
+                        Date currentWebsiteSslStartDate = null;
+                        Date currentWebsiteSslEndDate = null;
+
+
+                        // Extract the individual pieces of information from the current website SSL certificate if it is not null.
+                        if (sslCertificate != null) {
+                            currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
+                            currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
+                            currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
+                            currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
+                            currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
+                            currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
+                            currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
+                            currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
+                        }
+
+                        // Initialize `String` variables to store the SSL certificate dates.  `Strings` are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
+                        String currentWebsiteSslStartDateString = "";
+                        String currentWebsiteSslEndDateString = "";
+                        String pinnedDomainSslStartDateString = "";
+                        String pinnedDomainSslEndDateString = "";
+
+                        // Convert the `Dates` to `Strings` if they are not `null`.
+                        if (currentWebsiteSslStartDate != null) {
+                            currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
+                        }
+
+                        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(), getString(R.string.ssl_certificate_mismatch));
+                        }
+                    }
                 }
             }
 
             // Handle SSL Certificate errors.
             @Override
             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-                // 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 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(), getString(R.string.ssl_certificate_error));
+                }
             }
         });
 
         // Get a handle for the progress bar.
-        final ProgressBar progressBar = (ProgressBar) findViewById(R.id.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);
+                            });
+                }
+
                 progressBar.setProgress(progress);
                 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);
                 }
@@ -903,13 +1211,10 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         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));
-            }
+        mainWebView.setDownloadListener((url, userAgent, contentDisposition, mimetype, 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.
@@ -924,6 +1229,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // 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();
 
@@ -962,6 +1270,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         thirdPartyCookiesEnabled = false;
         domStorageEnabled = false;
         saveFormDataEnabled = false;
+        nightMode = false;
 
         // Initialize `webViewTitle`.
         webViewTitle = getString(R.string.no_title);
@@ -1022,12 +1331,33 @@ 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;
         }
     }
 
@@ -1109,6 +1439,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
         MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_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);
         MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
         MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
@@ -1124,13 +1455,30 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // Enable third-party cookies if first-party cookies are enabled.
         toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
 
-        // Enable DOM Storage if JavaScript is enabled.
+        // Enable `DOM Storage` if JavaScript is enabled.
         toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
 
-        // Enable Clear Cookies if there are any.
+        // Enable `Clear Cookies` if there are any.
         clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
 
-        // Enable Clear Form Data is there is any.
+        // Get a count of the number of files in the `Local Storage` directory.
+        File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
+        int localStorageDirectoryNumberOfFiles = 0;
+        if (localStorageDirectory.exists()) {
+            localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
+        }
+
+        // Get a count of the number of files in the `IndexedDB` directory.
+        File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
+        int indexedDBDirectoryNumberOfFiles = 0;
+        if (indexedDBDirectory.exists()) {
+            indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
+        }
+
+        // Enable `Clear DOM Storage` if there is any.
+        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
+
+        // Enable `Clear Form Data` is there is any.
         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
         clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
 
@@ -1142,47 +1490,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;
         }
@@ -1333,11 +1681,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
@@ -1365,11 +1710,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
@@ -1385,6 +1727,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                                         // Delete the DOM Storage.
                                         WebStorage webStorage = WebStorage.getInstance();
                                         webStorage.deleteAllData();
+
+                                        // Manually remove `IndexedDB` if it exists.
+                                        try {
+                                            privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                                        } catch (IOException e) {
+                                            // Do nothing if an error is thrown.
+                                        }
                                 }
                             }
                         })
@@ -1393,11 +1742,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
@@ -1465,12 +1811,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
             case R.id.share:
                 // Setup the share string.
-                String shareString;
-                if (webViewTitle != null) {
-                    shareString = webViewTitle + " – " + urlTextBox.getText().toString();
-                } else {
-                    shareString = urlTextBox.getText().toString();
-                }
+                String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
 
                 // Create the share intent.
                 Intent shareIntent = new Intent();
@@ -1489,19 +1830,13 @@ 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.
-                        inputMethodManager.showSoftInput(findOnPageEditText, 0);
-                    }
+                    // Display the keyboard.  `0` sets no input flags.
+                    inputMethodManager.showSoftInput(findOnPageEditText, 0);
                 }, 200);
                 return true;
 
@@ -1512,14 +1847,17 @@ 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.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;
@@ -1571,13 +1909,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:
@@ -1590,22 +1922,22 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 startActivity(downloadManagerIntent);
                 break;
 
-            case R.id.settings:
+            case R.id.domains:
                 // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`.
                 currentDomainName = "";
 
-                // Launch `SettingsActivity`.
-                Intent settingsIntent = new Intent(this, SettingsActivity.class);
-                startActivity(settingsIntent);
+                // Launch `DomainsActivity`.
+                Intent domainsIntent = new Intent(this, DomainsActivity.class);
+                startActivity(domainsIntent);
                 break;
 
-            case R.id.domains:
+            case R.id.settings:
                 // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`.
                 currentDomainName = "";
 
-                // Launch `DomainsActivity`.
-                Intent domainsIntent = new Intent(this, DomainsActivity.class);
-                startActivity(domainsIntent);
+                // Launch `SettingsActivity`.
+                Intent settingsIntent = new Intent(this, SettingsActivity.class);
+                startActivity(settingsIntent);
                 break;
 
             case R.id.guide:
@@ -1651,10 +1983,16 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     WebStorage webStorage = WebStorage.getInstance();
                     webStorage.deleteAllData();
 
-                    // Manually delete the DOM storage directory, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
+                    // 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.
                         privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+                        // We have to use multiple commands 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.
                     }
@@ -1774,6 +2112,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:
@@ -1784,25 +2125,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`.
@@ -1817,35 +2152,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`.
@@ -1861,36 +2190,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(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`.
@@ -1907,36 +2227,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(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`.
@@ -1945,10 +2256,86 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         }
     }
 
+    @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);
+        EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
 
         // Create the bookmark shortcut based on formattedUrlString.
         Intent bookmarkShortcut = new Intent();
@@ -1985,7 +2372,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`.
@@ -2004,6 +2391,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.
@@ -2033,7 +2423,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`.
@@ -2052,6 +2442,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.
@@ -2059,10 +2452,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
@@ -2075,6 +2577,26 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         sslErrorHandler.proceed();
     }
 
+    @Override
+    public void onSslMismatchBack() {
+        if (mainWebView.canGoBack()) {  // There is a back page in the history.
+            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
+            navigatingHistory = true;
+
+            // Go back.
+            mainWebView.goBack();
+        } else {  // There are no pages to go back to.
+            // Load a blank page
+            loadUrl("");
+        }
+    }
+
+    @Override
+    public void onSslMismatchProceed() {
+        // Do not check the pinned SSL certificate for this domain again until the domain changes.
+        ignorePinnedSslCertificate = true;
+    }
+
     @Override
     public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
         // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
@@ -2093,21 +2615,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();
         }
     }
 
@@ -2154,9 +2685,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         }
 
         loadUrl(formattedUrlString);
-
-        // Hide the keyboard so we can see the webpage.  `0` indicates no additional flags.
-        inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
     }
 
 
@@ -2384,6 +2912,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             // Set the new `hostname` as the `currentDomainName`.
             currentDomainName = hostName;
 
+            // Reset `ignorePinnedSslCertificate`.
+            ignorePinnedSslCertificate = false;
+
             // Reset `favoriteIconBitmap` and display it in the `appbar`.
             favoriteIconBitmap = favoriteIconDefaultBitmap;
             favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
@@ -2439,10 +2970,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.
@@ -2450,6 +2982,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 currentHostDomainSettingsCursor.moveToFirst();
 
                 // Get the settings from the cursor.
+                domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
                 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
                 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
                 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
@@ -2458,6 +2991,44 @@ 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));
+                pinnedDomainSslIssuedToUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
+                pinnedDomainSslIssuedByCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
+                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;
+                } else {
+                    pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+                }
+
+                // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
+                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
+                    pinnedDomainSslEndDate = null;
+                } else {
+                    pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                }
 
                 // Close `currentHostDomainSettingsCursor`.
                 currentHostDomainSettingsCursor.close();
@@ -2527,6 +3098,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);
@@ -2534,6 +3110,18 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
                 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
 
+                // Reset the pinned SSL certificate information.
+                domainSettingsDatabaseId = -1;
+                pinnedDomainSslCertificate = false;
+                pinnedDomainSslIssuedToCNameString = "";
+                pinnedDomainSslIssuedToONameString = "";
+                pinnedDomainSslIssuedToUNameString = "";
+                pinnedDomainSslIssuedByCNameString = "";
+                pinnedDomainSslIssuedByONameString = "";
+                pinnedDomainSslIssuedByUNameString = "";
+                pinnedDomainSslStartDate = null;
+                pinnedDomainSslEndDate = null;
+
                 // Set third-party cookies status if API >= 21.
                 if (Build.VERSION.SDK_INT >= 21) {
                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
@@ -2676,4 +3264,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);
+        }
+    }
 }