]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Improve the application delay of night mode CSS.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index a0046280162d4c7bb62edad317322c5cb17f6b7d..7549f5ca2e1a4b15c3cecbfde3d5fab96b1fb7e6 100644 (file)
@@ -41,6 +41,7 @@ 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;
@@ -59,7 +60,9 @@ import android.support.v7.app.AppCompatActivity;
 import android.support.v7.app.AppCompatDialogFragment;
 import android.support.v7.widget.Toolbar;
 import android.text.Editable;
+import android.text.Spanned;
 import android.text.TextWatcher;
+import android.text.style.ForegroundColorSpan;
 import android.util.Patterns;
 import android.view.ContextMenu;
 import android.view.GestureDetector;
@@ -72,7 +75,9 @@ 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.ValueCallback;
 import android.webkit.WebBackForwardList;
 import android.webkit.WebChromeClient;
 import android.webkit.WebResourceResponse;
@@ -93,6 +98,8 @@ import com.stoutner.privacybrowser.BuildConfig;
 import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
+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.DomainsDatabaseHelper;
@@ -102,6 +109,7 @@ import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
@@ -109,6 +117,7 @@ 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;
@@ -116,20 +125,23 @@ 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 {
+        HttpAuthenticationDialog.HttpAuthenticationListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener,
+        DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener {
 
-    // `appBar` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applyAppSettings()`.
-    public static ActionBar appBar;
+    // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`,
+    // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `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`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`,
+    // and `ViewSslCertificateDialog`.  It is also used in `onCreate()`, `onCreateHomeScreenShortcutCreate()`, 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()`.
@@ -141,11 +153,28 @@ 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;
 
-    // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`.
+    // 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()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
     private boolean navigatingHistory;
 
     // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
@@ -158,7 +187,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     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()`.
@@ -179,22 +208,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;
 
@@ -207,7 +238,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()`.
@@ -225,9 +256,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;
 
@@ -243,7 +277,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()`.
@@ -261,26 +295,63 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
     private Toolbar supportAppBar;
 
-    // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `loadUrl()`.
+    // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
     private EditText urlTextBox;
 
+    // `redColorSpan` is used in `onCreate()` and `highlightUrlText()`.
+    private ForegroundColorSpan redColorSpan;
+
+    // `initialGrayColorSpan` is sued in `onCreate()` and `highlightUrlText()`.
+    private ForegroundColorSpan initialGrayColorSpan;
+
+    // `finalGrayColorSpam` is used in `onCreate()` and `highlightUrlText()`.
+    private ForegroundColorSpan finalGrayColorSpan;
+
     // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
     private View adView;
 
     // `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;
 
     // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
     private RelativeLayout mainWebViewRelativeLayout;
 
+    // `urlIsLoading` is used in `onCreate()`, `loadUrl()`, and `applyDomainSettings()`.
+    private boolean urlIsLoading;
+
+    // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
+    private boolean pinnedDomainSslCertificate;
+
+
     @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")
+    // Remove Android Studio's warning about deprecations.  We have to use the deprecated `getColor()` until API >= 23.
+    @SuppressWarnings("deprecation")
     protected void onCreate(Bundle savedInstanceState) {
+        // Get a handle for `sharedPreferences`.  `this` references the current context.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+        // Get the theme preference.
+        darkTheme = sharedPreferences.getBoolean("dark_theme", false);
+
+        // Set the activity theme.
+        if (darkTheme) {
+            setTheme(R.style.PrivacyBrowserDark);
+        } else {
+            setTheme(R.style.PrivacyBrowserLight);
+        }
+
+        // Run the default commands.
         super.onCreate(savedInstanceState);
+
+        // Set the content view.
         setContentView(R.layout.main_drawerlayout);
 
         // Get a handle for `inputMethodManager`.
@@ -298,8 +369,31 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         appBar.setCustomView(R.layout.url_app_bar);
         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
-        // Set the "go" button on the keyboard to load the URL in urlTextBox.
+        // Initialize the `ForegroundColorSpans` and `StyleSpan` for highlighting `urlTextBox`.  We have to use the deprecated `getColor()` until API >= 23.
+        redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+        initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+        finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
+
+        // Get a handle for `urlTextBox`.
         urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.url_edittext);
+
+        // 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();
+                }
+            }
+        });
+
+        // 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) {
@@ -547,8 +641,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
                     navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
 
-                    // Hide the keyboard so we can see the navigation menu.  `0` indicates no additional flags.
+                    // Hide the keyboard (if displayed) so we can see the navigation menu.  `0` indicates no additional flags.
                     inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+
+                    // Clear the focus from `urlTextBox` if it has it.
+                    urlTextBox.clearFocus();
                 }
             }
         });
@@ -645,30 +742,52 @@ 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);
+                }
 
                 // Check to see if we are waiting on Orbot.
                 if (!waitingForOrbot) {  // We are not waiting on Orbot, so we need to process the URL.
                     // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
                     formattedUrlString = url;
 
-                    // Display the loading URL is the URL text box.
-                    urlTextBox.setText(url);
+                    // Display the formatted URL text.
+                    urlTextBox.setText(formattedUrlString);
+
+                    // Apply text highlighting to `urlTextBox`.
+                    highlightUrlText();
 
                     // Apply any custom domain settings if the URL was loaded by navigating history.
                     if (navigatingHistory) {
                         applyDomainSettings(url);
                     }
+
+                    // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
+                    urlIsLoading = true;
                 }
             }
 
-            // 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.
+                urlIsLoading = false;
+
                 // Clear the cache and history if Incognito Mode is enabled.
                 if (incognitoModeEnabled) {
                     // Clear the cache.  `true` includes disk files.
@@ -696,7 +815,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                         // Set `formattedUrlString` to `""`.
                         formattedUrlString = "";
 
-                        // Update `urlTextBox`.
                         urlTextBox.setText(formattedUrlString);
 
                         // Request focus for `urlTextBox`.
@@ -705,7 +823,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                         // Display the keyboard.
                         inputMethodManager.showSoftInput(urlTextBox, 0);
 
-                        // Apply the domain settings.
+                        // Apply the domain settings.  This clears any settings from the previous domain.
                         applyDomainSettings(formattedUrlString);
                     } else {  // `WebView` has loaded a webpage.
                         // Set `formattedUrlString`.
@@ -713,24 +831,108 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
                         // Only update `urlTextBox` if the user is not typing in it.
                         if (!urlTextBox.hasFocus()) {
+                            // Display the formatted URL text.
                             urlTextBox.setText(formattedUrlString);
+
+                            // Apply text highlighting to `urlTextBox`.
+                            highlightUrlText();
                         }
                     }
 
-                    // 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));
+                }
             }
         });
 
@@ -743,10 +945,42 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             public void onProgressChanged(WebView view, int progress) {
                 progressBar.setProgress(progress);
                 if (progress < 100) {
+                    // Show the progress bar.
                     progressBar.setVisibility(View.VISIBLE);
                 } else {
+                    // Hide the progress bar.
                     progressBar.setVisibility(View.GONE);
 
+                    // Inject the night mode CSS if night mode is enabled.
+                    if (nightMode) {  // Night mode is enabled.
+                        // `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)})()", new ValueCallback<String>() {
+                            @Override
+                            public void onReceiveValue(String 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 = new Runnable() {
+                                    public void run() {
+                                        mainWebView.setVisibility(View.VISIBLE);
+                                    }
+                                };
+
+                                // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds.
+                                displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
+                            }
+                        });
+                    } else {  // Night mode is disabled.
+                        // Display `mainWebView` in case it was hidden before loading domain settings.
+                        mainWebView.setVisibility(View.VISIBLE);
+                    }
+
                     //Stop the `SwipeToRefresh` indicator if it is running
                     swipeRefreshLayout.setRefreshing(false);
                 }
@@ -832,7 +1066,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             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));
+                downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
             }
         });
 
@@ -848,6 +1082,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();
 
@@ -886,6 +1123,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         thirdPartyCookiesEnabled = false;
         domStorageEnabled = false;
         saveFormDataEnabled = false;
+        nightMode = false;
 
         // Initialize `webViewTitle`.
         webViewTitle = getString(R.string.no_title);
@@ -946,12 +1184,21 @@ 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;
         }
     }
 
@@ -1033,6 +1280,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);
@@ -1048,13 +1296,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());
 
@@ -1066,47 +1331,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;
         }
@@ -1309,6 +1574,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.
+                                        }
                                 }
                             }
                         })
@@ -1389,12 +1661,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();
@@ -1413,17 +1680,15 @@ 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()
-                {
+                // 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.
+                        // Display the keyboard.  `0` sets no input flags.
                         inputMethodManager.showSoftInput(findOnPageEditText, 0);
                     }
                 }, 200);
@@ -1437,13 +1702,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
 
                 // 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;
@@ -1495,7 +1760,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));
+                urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
                 break;
 
             case R.id.bookmarks:
@@ -1575,10 +1840,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.
                     }
@@ -1721,7 +1992,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     @Override
                     public boolean onMenuItemClick(MenuItem item) {
                         // Save the link URL in a `ClipData`.
-                        ClipData srcAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), linkUrl);
+                        ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
 
                         // Set the `ClipData` as the clipboard's primary clip.
                         clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
@@ -1764,7 +2035,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     @Override
                     public boolean onMenuItemClick(MenuItem item) {
                         // Save the email address in a `ClipData`.
-                        ClipData srcEmailTypeClipData = ClipData.newPlainText(getResources().getString(R.string.email_address), linkUrl);
+                        ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
 
                         // Set the `ClipData` as the clipboard's primary clip.
                         clipboardManager.setPrimaryClip(srcEmailTypeClipData);
@@ -1799,7 +2070,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     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));
+                        downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
                         return false;
                     }
                 });
@@ -1809,7 +2080,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     @Override
                     public boolean onMenuItemClick(MenuItem item) {
                         // Save the image URL in a `ClipData`.
-                        ClipData srcImageTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl);
+                        ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
 
                         // Set the `ClipData` as the clipboard's primary clip.
                         clipboardManager.setPrimaryClip(srcImageTypeClipData);
@@ -1845,7 +2116,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     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));
+                        downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
                         return false;
                     }
                 });
@@ -1855,7 +2126,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     @Override
                     public boolean onMenuItemClick(MenuItem item) {
                         // Save the image URL in a `ClipData`.
-                        ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl);
+                        ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
 
                         // Set the `ClipData` as the clipboard's primary clip.
                         clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
@@ -1983,10 +2254,26 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         }
     }
 
+    @Override
+    public void onHttpAuthenticationCancel() {
+        // Cancel the `HttpAuthHandler`.
+        httpAuthHandler.cancel();
+    }
+
+    @Override
+    public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
+        // Get handles for the `EditTexts`.
+        EditText usernameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
+        EditText passwordEditText = (EditText) 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
@@ -1999,6 +2286,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.
@@ -2090,6 +2397,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
         // Load the URL.
         mainWebView.loadUrl(url, customHeaders);
+
+        // Set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website.
+        urlIsLoading = true;
     }
 
     public void findPreviousOnPage(View view) {
@@ -2160,6 +2470,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             // Set the proxy.  `this` refers to the current activity where an `AlertDialog` might be displayed.
             OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
 
+            // Set the `appBar` background to indicate proxying through Orbot is enabled.  `this` refers to the context.
+            if (darkTheme) {
+                appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
+            } else {
+                appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
+            }
+
             // Display a message to the user if we are waiting on Orbot.
             if (!orbotStatus.equals("ON")) {
                 // Set `waitingForOrbot`.
@@ -2187,6 +2504,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             // Reset the proxy to default.  The host is `""` and the port is `"0"`.
             OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
 
+            // Set the default `appBar` background.  `this` refers to the context.
+            if (darkTheme) {
+                appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
+            } else {
+                appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
+            }
+
             // Reset `waitingForOrbot.
             waitingForOrbot = false;
         }
@@ -2291,6 +2615,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));
@@ -2346,12 +2673,19 @@ 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 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.
                 Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
                 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);
@@ -2360,6 +2694,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();
@@ -2369,24 +2741,58 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
                 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
-                mainWebView.getSettings().setTextZoom(fontSize);
+
+                // Apply the font size.
+                if (fontSize == 0) {  // Apply the default font size.
+                    mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
+                } else {  // Apply the specified font size.
+                    mainWebView.getSettings().setTextZoom(fontSize);
+                }
 
                 // Set third-party cookies status if API >= 21.
                 if (Build.VERSION.SDK_INT >= 21) {
                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
                 }
 
-                // Set the user agent.
-                if (userAgentString.equals("WebView default user agent")) {
-                    // Set the user agent to `""`, which uses the default value.
-                    mainWebView.getSettings().setUserAgentString("");
-                } else {
-                    // Use the selected user agent.
-                    mainWebView.getSettings().setUserAgentString(userAgentString);
+                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.  <https://redmine.stoutner.com/issues/160>
+                if (!urlIsLoading) {
+                    switch (userAgentString) {
+                        case "System default user agent":
+                            // Set the user agent according to the system default.
+                            switch (defaultUserAgentString) {
+                                case "WebView default user agent":
+                                    // Set the user agent to `""`, which uses the default value.
+                                    mainWebView.getSettings().setUserAgentString("");
+                                    break;
+
+                                case "Custom user agent":
+                                    // Set the custom user agent.
+                                    mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+                                    break;
+
+                                default:
+                                    // Use the selected user agent.
+                                    mainWebView.getSettings().setUserAgentString(defaultUserAgentString);
+                            }
+                            break;
+
+                        case "WebView default user agent":
+                            // Set the user agent to `""`, which uses the default value.
+                            mainWebView.getSettings().setUserAgentString("");
+                            break;
+
+                        default:
+                            // Use the selected user agent.
+                            mainWebView.getSettings().setUserAgentString(userAgentString);
+                    }
                 }
 
                 // Set a green background on `urlTextBox` to indicate that custom domain settings are being used.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
-                urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_green));
+                if (darkTheme) {
+                    urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
+                } else {
+                    urlAppBarRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
+                }
             } else {  // The URL we are loading does not have custom domain settings.  Load the defaults.
                 // Store the values from `sharedPreferences` in variables.
                 javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
@@ -2394,9 +2800,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
                 domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
                 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
-                String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
-                String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
-                String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
+
+                // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
+                if (nightMode) {
+                    javaScriptEnabled = true;
+                }
 
                 // Apply the default settings.
                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
@@ -2405,31 +2813,44 @@ 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);
                 }
 
-                // Set the default user agent.
-                switch (userAgentString) {
-                    case "WebView default user agent":
-                        // Set the user agent to `""`, which uses the default value.
-                        mainWebView.getSettings().setUserAgentString("");
-                        break;
-
-                    case "Custom user agent":
-                        // Set the custom user agent.
-                        mainWebView.getSettings().setUserAgentString(customUserAgentString);
-                        break;
-
-                    default:
-                        // Use the selected user agent.
-                        mainWebView.getSettings().setUserAgentString(userAgentString);
-                        break;
+                // Only set the user agent if the webpage is not currently loading.  Otherwise, changing the user agent on redirects can cause the original website to reload.  <https://redmine.stoutner.com/issues/160>
+                if (!urlIsLoading) {
+                    switch (defaultUserAgentString) {
+                        case "WebView default user agent":
+                            // Set the user agent to `""`, which uses the default value.
+                            mainWebView.getSettings().setUserAgentString("");
+                            break;
+
+                        case "Custom user agent":
+                            // Set the custom user agent.
+                            mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString);
+                            break;
+
+                        default:
+                            // Use the selected user agent.
+                            mainWebView.getSettings().setUserAgentString(defaultUserAgentString);
+                    }
                 }
 
                 // Set a transparent background on `urlTextBox`.  We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
-                urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.drawable.url_bar_background_transparent));
+                urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
             }
 
             // Close `domainsDatabaseHelper`.
@@ -2488,23 +2909,39 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
             firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_enabled);
         } else {  // First-party cookies are disabled.
-            firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled);
+            if (darkTheme) {
+                firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled_dark);
+            } else {
+                firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled_light);
+            }
         }
 
         // Update `domStorageIcon`.
         if (javaScriptEnabled && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
             domStorageIconMenuItem.setIcon(R.drawable.dom_storage_enabled);
         } else if (javaScriptEnabled) {  // JavaScript is enabled but DOM storage is disabled.
-            domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled);
+            if (darkTheme) {
+                domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
+            } else {
+                domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
+            }
         } else {  // JavaScript is disabled, so DOM storage is ghosted.
-            domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted);
+            if (darkTheme) {
+                domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
+            } else {
+                domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
+            }
         }
 
         // Update `formDataIcon`.
         if (saveFormDataEnabled) {  // Form data is enabled.
             formDataIconMenuItem.setIcon(R.drawable.form_data_enabled);
         } else {  // Form data is disabled.
-            formDataIconMenuItem.setIcon(R.drawable.form_data_disabled);
+            if (darkTheme) {
+                formDataIconMenuItem.setIcon(R.drawable.form_data_disabled_dark);
+            } else {
+                formDataIconMenuItem.setIcon(R.drawable.form_data_disabled_light);
+            }
         }
 
         // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.  `this` references the current activity.
@@ -2512,4 +2949,22 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             ActivityCompat.invalidateOptionsMenu(this);
         }
     }
+
+    private void highlightUrlText() {
+        String urlString = urlTextBox.getText().toString();
+
+        if (urlString.startsWith("http://")) {  // Highlight connections that are not encrypted.
+            urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        } else if (urlString.startsWith("https://")) {  // Highlight connections that are encrypted.
+            urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        }
+
+        // Get the index of the `/` immediately after the domain name.
+        int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
+
+        // De-emphasize the text after the domain name.
+        if (endOfDomainName > 0) {
+            urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        }
+    }
 }