Add SSL certificate pinning. Implements https://redmine.stoutner.com/issues/54.
authorSoren Stoutner <soren@stoutner.com>
Wed, 23 Aug 2017 05:34:33 +0000 (22:34 -0700)
committerSoren Stoutner <soren@stoutner.com>
Wed, 23 Aug 2017 05:34:33 +0000 (22:34 -0700)
31 files changed:
.idea/dictionaries/soren.xml
app/src/main/assets/de/about_licenses.html
app/src/main/assets/en/about_licenses.html
app/src/main/assets/en/images/ic_vpn_lock.png [new file with mode: 0644]
app/src/main/assets/es/about_licenses.html
app/src/main/assets/it/about_licenses.html
app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/definitions/WrapVerticalContentViewPager.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java
app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java
app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java
app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java
app/src/main/res/drawable/ssl_certificate_disabled_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/ssl_certificate_disabled_light.xml [new file with mode: 0644]
app/src/main/res/drawable/ssl_certificate_enabled_dark.xml [new file with mode: 0644]
app/src/main/res/drawable/ssl_certificate_enabled_light.xml [new file with mode: 0644]
app/src/main/res/layout/about_coordinatorlayout.xml
app/src/main/res/layout/add_domain_dialog.xml
app/src/main/res/layout/domain_settings_fragment.xml
app/src/main/res/layout/pinned_ssl_certificate_mismatch_linearlayout.xml [new file with mode: 0644]
app/src/main/res/layout/pinned_ssl_certificate_mismatch_scrollview.xml [new file with mode: 0644]
app/src/main/res/layout/view_ssl_certificate.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values/attrs.xml
app/src/main/res/values/colors.xml
app/src/main/res/values/strings.xml
app/src/main/res/values/styles.xml

index 4ee70c2..16a47ed 100644 (file)
@@ -77,6 +77,7 @@
       <w>panopticlick</w>
       <w>parameterized</w>
       <w>parentfolder</w>
+      <w>pinnedsslcertificate</w>
       <w>programatically</w>
       <w>proxying</w>
       <w>qwant</w>
       <w>snackbar</w>
       <w>snackbars</w>
       <w>softkeyboard</w>
+      <w>sslenddate</w>
+      <w>sslissuedbycommonname</w>
+      <w>sslissuedbyorganization</w>
+      <w>sslissuedbyorganizationalunit</w>
+      <w>sslissuedtocommonname</w>
+      <w>sslissuedtoorganization</w>
+      <w>sslissuedtoorganizationalunit</w>
+      <w>sslstartdate</w>
       <w>subdomain</w>
       <w>subdomains</w>
       <w>subfolders</w>
       <w>uidh</w>
       <w>uids</w>
       <w>uname</w>
+      <w>uncheck</w>
       <w>useragent</w>
       <w>useragentname</w>
       <w>useragentstring</w>
index 3269228..7899ba4 100644 (file)
         <p><img class="icon" src="../en/images/ic_vertical_align_bottom.png"> ic_vertical_align_bottom.</p>
         <p><img class="icon" src="../en/images/ic_vertical_align_top.png"> ic_vertical_align_top.</p>
         <p><img class="icon" src="../en/images/ic_visibility_off.png"> ic_visibility_off.</p>
+        <p><img class="icon" src="../en/images/ic_vpn_lock.png"> ic_vpn_lock.</p>
         <p><img class="icon" src="../en/images/ic_web.png"> ic_web.</p>
 
         <hr/>
index 18a7edb..cad98dc 100644 (file)
         <p><img class="icon" src="images/ic_vertical_align_bottom.png"> ic_vertical_align_bottom.</p>
         <p><img class="icon" src="images/ic_vertical_align_top.png"> ic_vertical_align_top.</p>
         <p><img class="icon" src="images/ic_visibility_off.png"> ic_visibility_off.</p>
+        <p><img class="icon" src="images/ic_vpn_lock.png"> ic_vpn_lock.</p>
         <p><img class="icon" src="images/ic_web.png"> ic_web.</p>
 
         <hr/>
diff --git a/app/src/main/assets/en/images/ic_vpn_lock.png b/app/src/main/assets/en/images/ic_vpn_lock.png
new file mode 100644 (file)
index 0000000..5adb9cf
Binary files /dev/null and b/app/src/main/assets/en/images/ic_vpn_lock.png differ
index 1c50064..55df760 100644 (file)
         <p><img class="icon" src="../en/images/ic_vertical_align_bottom.png"> ic_vertical_align_bottom.</p>
         <p><img class="icon" src="../en/images/ic_vertical_align_top.png"> ic_vertical_align_top.</p>
         <p><img class="icon" src="../en/images/ic_visibility_off.png"> ic_visibility_off.</p>
+        <p><img class="icon" src="../en/images/ic_vpn_lock.png"> ic_vpn_lock.</p>
         <p><img class="icon" src="../en/images/ic_web.png"> ic_web.</p>
 
         <hr/>
index efe8d20..23ee075 100644 (file)
         <p><img class="icon" src="../en/images/ic_vertical_align_bottom.png"> ic_vertical_align_bottom.</p>
         <p><img class="icon" src="../en/images/ic_vertical_align_top.png"> ic_vertical_align_top.</p>
         <p><img class="icon" src="../en/images/ic_visibility_off.png"> ic_visibility_off.</p>
+        <p><img class="icon" src="../en/images/ic_vpn_lock.png"> ic_vpn_lock.</p>
         <p><img class="icon" src="../en/images/ic_web.png"> ic_web.</p>
 
         <hr/>
index 0e023d4..f71a138 100644 (file)
@@ -21,6 +21,7 @@ package com.stoutner.privacybrowser.activities;
 
 import android.content.Context;
 import android.database.Cursor;
+import android.net.http.SslCertificate;
 import android.os.Bundle;
 import android.os.Handler;
 import android.support.design.widget.FloatingActionButton;
@@ -38,6 +39,7 @@ import android.view.ViewGroup;
 import android.widget.CursorAdapter;
 import android.widget.EditText;
 import android.widget.ListView;
+import android.widget.RadioButton;
 import android.widget.Spinner;
 import android.widget.Switch;
 import android.widget.TextView;
@@ -518,6 +520,9 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         EditText customUserAgentEditText = (EditText) findViewById(R.id.domain_settings_custom_user_agent_edittext);
         Spinner fontSizeSpinner = (Spinner) findViewById(R.id.domain_settings_font_size_spinner);
         Spinner displayWebpageImagesSpinner = (Spinner) findViewById(R.id.domain_settings_display_webpage_images_spinner);
+        Switch pinnedSslCertificateSwitch = (Switch) findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
+        RadioButton savedSslCertificateRadioButton = (RadioButton) findViewById(R.id.saved_ssl_certificate_radiobutton);
+        RadioButton currentWebsiteCertificateRadioButton = (RadioButton) findViewById(R.id.current_website_certificate_radiobutton);
 
         // Extract the data for the domain settings.
         String domainNameString = domainNameEditText.getText().toString();
@@ -529,6 +534,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         int userAgentPositionInt = userAgentSpinner.getSelectedItemPosition();
         int fontSizePositionInt = fontSizeSpinner.getSelectedItemPosition();
         int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
+        boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
 
         // Get the data for the `Spinners` from the entry values string arrays.
         String userAgentString = getResources().getStringArray(R.array.domain_settings_user_agent_entry_values)[userAgentPositionInt];
@@ -541,8 +547,33 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         }
 
         // Save the domain settings.
-        domainsDatabaseHelper.saveDomain(currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean, userAgentString, fontSizeInt,
-                displayWebpageImagesInt);
+        if (savedSslCertificateRadioButton.isChecked()) {  // The current certificate is being used.
+            // Update the database except for the certificate.
+            domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean,
+                    formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate);
+        } else if (currentWebsiteCertificateRadioButton.isChecked()) {  // The certificate is being updated with the current website certificate.
+            // Get the current website SSL certificate.
+            SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
+
+            // Store the values from the SSL certificate.
+            String issuedToCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName();
+            String issuedToOrganization = currentWebsiteSslCertificate.getIssuedTo().getOName();
+            String issuedToOrganizationalUnit = currentWebsiteSslCertificate.getIssuedTo().getUName();
+            String issuedByCommonName = currentWebsiteSslCertificate.getIssuedBy().getCName();
+            String issuedByOrganization = currentWebsiteSslCertificate.getIssuedBy().getOName();
+            String issuedByOrganizationalUnit = currentWebsiteSslCertificate.getIssuedBy().getUName();
+            long startDateLong = currentWebsiteSslCertificate.getValidNotBeforeDate().getTime();
+            long endDateLong = currentWebsiteSslCertificate.getValidNotAfterDate().getTime();
+
+            // Update the database.
+            domainsDatabaseHelper.updateDomainWithCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean,
+                    userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization, issuedByOrganizationalUnit, startDateLong,
+                    endDateLong);
+        } else {  // No certificate is selected.
+            // Update the database, with PINNED_SSL_CERTIFICATE set to false.
+            domainsDatabaseHelper.updateDomainExceptCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean,
+                    userAgentString, fontSizeInt, displayWebpageImagesInt, false);
+        }
     }
 
     private void populateDomainsListView(final int highlightedDomainDatabaseId) {
index 0d8534c..975814a 100644 (file)
@@ -95,6 +95,7 @@ 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.PinnedSslCertificateMismatchDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
@@ -112,6 +113,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;
@@ -119,22 +121,22 @@ 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 {
+        PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener {
 
     // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`,
     // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`,
     // `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
     public 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()`.
@@ -149,11 +151,23 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `reloadOnRestartBoolean` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`
     public static boolean reloadOnRestartBoolean;
 
+    // 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 boolean pinnedDomainSslCertificate;
+    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`.
@@ -166,7 +180,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()`.
@@ -233,9 +247,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;
 
@@ -296,6 +313,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `urlIsLoading` is used in `onCreate()`, `loadUrl()`, and `applyDomainSettings()`.
     private boolean urlIsLoading;
 
+
     @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")
@@ -790,20 +808,100 @@ 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(), getResources().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(), getResources().getString(R.string.ssl_certificate_error));
+                }
             }
         });
 
@@ -1507,8 +1605,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 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()
-                {
+                findOnPageEditText.postDelayed(new Runnable() {
                     @Override
                     public void run()
                     {
@@ -2097,6 +2194,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.
@@ -2406,6 +2523,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));
@@ -2472,6 +2592,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);
@@ -2480,6 +2601,27 @@ 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));
+                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 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();
@@ -2556,6 +2698,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);
diff --git a/app/src/main/java/com/stoutner/privacybrowser/definitions/WrapVerticalContentViewPager.java b/app/src/main/java/com/stoutner/privacybrowser/definitions/WrapVerticalContentViewPager.java
new file mode 100644 (file)
index 0000000..8b1d978
--- /dev/null
@@ -0,0 +1,61 @@
+package com.stoutner.privacybrowser.definitions;
+
+/*
+ * Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class WrapVerticalContentViewPager extends ViewPager {
+    // Setup the default constructors.
+    public WrapVerticalContentViewPager(Context context) {
+        super(context);
+    }
+
+    public WrapVerticalContentViewPager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Perform an initial `super.onMeasure`, which populates `getChildCount`.
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        // Initialize `maximumHeight`.
+        int maximumHeight = 0;
+
+        // Find the maximum height of each of the child views.
+        for (int i = 0; i < getChildCount(); i++) {
+            View childView = getChildAt(i);
+
+            // Measure the child view height with no constraints.
+            childView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+
+            // Store the child's height if it is larger than `maximumHeight`.
+            if (childView.getMeasuredHeight() > maximumHeight) {
+                maximumHeight = childView.getMeasuredHeight();
+            }
+        }
+
+        // Perform a final `super.onMeasure` to set the `maximumHeight`.
+        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maximumHeight, MeasureSpec.EXACTLY));
+    }
+}
\ No newline at end of file
index dbda6c7..4741833 100644 (file)
@@ -86,13 +86,8 @@ public class CreateHomeScreenShortcutDialog extends AppCompatDialogFragment {
         // Set the view.  The parent view is `null` because it will be assigned by `AlertDialog`.
         dialogBuilder.setView(layoutInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null));
 
-        // Set an `onClick` listener on the negative button.
-        dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int which) {
-                // Do nothing if `Cancel` is clicked.
-            }
-        });
+        // Setup the negative button.  Using `null` closes the dialog without doing anything else.
+        dialogBuilder.setNegativeButton(R.string.cancel, null);
 
         // Set an `onClick` listener on the positive button.
         dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java
new file mode 100644 (file)
index 0000000..aa5b627
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.http.SslCertificate;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.TabLayout;
+import android.support.v4.view.PagerAdapter;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
+import android.support.v7.app.AppCompatDialogFragment;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.activities.MainWebViewActivity;
+import com.stoutner.privacybrowser.definitions.WrapVerticalContentViewPager;
+import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+// `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
+@SuppressLint("InflateParams")
+public class PinnedSslCertificateMismatchDialog extends AppCompatDialogFragment {
+    // `layoutInflater` is used in `onCreateDialog()` and `pagerAdapter`.
+    private LayoutInflater layoutInflater;
+
+    // The current website SSL certificate variables are used in `onCreateDialog()` and `pagerAdapter()`.
+    private String currentSslIssuedToCNameString;
+    private String currentSslIssuedToONameString;
+    private String currentSslIssuedToUNameString;
+    private String currentSslIssuedByCNameString;
+    private String currentSslIssuedByONameString;
+    private String currentSslIssuedByUNameString;
+    private Date currentSslStartDate;
+    private Date currentSslEndDate;
+
+    // The public interface is used to send information back to the parent activity.
+    public interface PinnedSslCertificateMismatchListener {
+        void onSslMismatchBack();
+
+        void onSslMismatchProceed();
+    }
+
+    // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`.
+    private PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener pinnedSslCertificateMismatchListener;
+
+    // Check to make sure that the parent activity implements the listener.
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        try {
+            pinnedSslCertificateMismatchListener = (PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener) context;
+        } catch(ClassCastException exception) {
+            throw new ClassCastException(context.toString() + " must implement PinnedSslCertificateMismatchListener");
+        }
+    }
+
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Get the activity's layout inflater.
+        layoutInflater = getActivity().getLayoutInflater();
+
+        // Use `AlertDialog.Builder` to create the `AlertDialog`.
+        AlertDialog.Builder dialogBuilder;
+
+        // Set the style according to the theme.
+        if (MainWebViewActivity.darkTheme) {
+            // Set the dialog theme.
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
+
+            // Set the icon.
+            dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark);
+        } else {
+            // Set the dialog theme.
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
+
+            // Set the icon.
+            dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light);
+        }
+
+        // Setup the neutral button.
+        dialogBuilder.setNeutralButton(R.string.update_ssl, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Initialize the `long` date variables.  If the date is `null`, a long value of `0` will be stored in the Domains database entry.
+                long currentSslStartDateLong = 0;
+                long currentSslEndDateLong = 0;
+
+                // Convert the `Dates` into `longs`.
+                if (currentSslStartDate != null) {
+                    currentSslStartDateLong = currentSslStartDate.getTime();
+                }
+
+                if (currentSslEndDate != null) {
+                    currentSslEndDateLong = currentSslEndDate.getTime();
+                }
+
+                // Initialize the database handler.  The two `nulls` do not specify the database name or a `CursorFactory`.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+                DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(getContext(), null, null, 0);
+
+                // Update the pinned SSL certificate for this domain.
+                domainsDatabaseHelper.updateCertificate(MainWebViewActivity.domainSettingsDatabaseId, currentSslIssuedToCNameString, currentSslIssuedToONameString, currentSslIssuedToUNameString, currentSslIssuedByCNameString, currentSslIssuedByONameString,
+                        currentSslIssuedByUNameString, currentSslStartDateLong, currentSslEndDateLong);
+
+                // Update the pinned SSL certificate global variables to match the information that is now in the database.
+                MainWebViewActivity.pinnedDomainSslIssuedToCNameString = currentSslIssuedToCNameString;
+                MainWebViewActivity.pinnedDomainSslIssuedToONameString = currentSslIssuedToONameString;
+                MainWebViewActivity.pinnedDomainSslIssuedToUNameString = currentSslIssuedToUNameString;
+                MainWebViewActivity.pinnedDomainSslIssuedByCNameString = currentSslIssuedByCNameString;
+                MainWebViewActivity.pinnedDomainSslIssuedByONameString = currentSslIssuedByONameString;
+                MainWebViewActivity.pinnedDomainSslIssuedByUNameString = currentSslIssuedByUNameString;
+                MainWebViewActivity.pinnedDomainSslStartDate = currentSslStartDate;
+                MainWebViewActivity.pinnedDomainSslEndDate = currentSslEndDate;
+            }
+        });
+
+        // Setup the negative button.
+        dialogBuilder.setNegativeButton(R.string.back, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Call the `onSslMismatchBack` public interface to send the `WebView` back one page.
+                pinnedSslCertificateMismatchListener.onSslMismatchBack();
+            }
+        });
+
+        // Setup the positive button.
+        dialogBuilder.setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Call the `onSslMismatchProceed` public interface.
+                pinnedSslCertificateMismatchListener.onSslMismatchProceed();
+            }
+        });
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.ssl_certificate_mismatch);
+
+        // Set the layout.  The parent view is `null` because it will be assigned by `AlertDialog`.
+        dialogBuilder.setView(layoutInflater.inflate(R.layout.pinned_ssl_certificate_mismatch_linearlayout, null));
+
+        // Create an `AlertDialog` from the `AlertDialog.Builder`
+        final AlertDialog alertDialog = dialogBuilder.create();
+
+        // Show the `AlertDialog` so the items in the layout can be modified.
+        alertDialog.show();
+
+        //  Setup `wrapVerticalContentViewPager`.
+        WrapVerticalContentViewPager wrapVerticalContentViewPager = (WrapVerticalContentViewPager) alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager);
+        wrapVerticalContentViewPager.setAdapter(new pagerAdapter());
+
+        // Setup the `TabLayout` and connect it to the `WrapVerticalContentViewPager`.
+        TabLayout tabLayout = (TabLayout) alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_tablayout);
+        tabLayout.setupWithViewPager(wrapVerticalContentViewPager);
+
+        // `onCreateDialog` requires the return of an `AlertDialog`.
+        return alertDialog;
+    }
+
+    private class pagerAdapter extends PagerAdapter {
+        @Override
+        public boolean isViewFromObject(View view, Object object) {
+            // Check to see if the `View` and the `Object` are the same.
+            return (view == object);
+        }
+
+        @Override
+        public int getCount() {
+            // There are two tabs.
+            return 2;
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            // Return the current tab title.
+            if (position == 0) {  // The current SSL certificate tab.
+                return getString(R.string.current_ssl);
+            } else {  // The pinned SSL certificate tab.
+                return getString(R.string.pinned_ssl);
+            }
+        }
+
+        @Override
+        public Object instantiateItem(ViewGroup container, int position) {
+            // Inflate the `ScrollView` for this tab.
+            ViewGroup tabViewGroup = (ViewGroup) layoutInflater.inflate(R.layout.pinned_ssl_certificate_mismatch_scrollview, container, false);
+
+            // Get handles for the `TextViews`.
+            TextView issuedToCNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_cname);
+            TextView issuedToONameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_oname);
+            TextView issuedToUNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_uname);
+            TextView issuedByCNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_cname);
+            TextView issuedByONameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_oname);
+            TextView issuedByUNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_uname);
+            TextView startDateTextView = (TextView) tabViewGroup.findViewById(R.id.start_date);
+            TextView endDateTextView = (TextView) tabViewGroup.findViewById(R.id.end_date);
+
+            // Setup the labels.
+            String cNameLabel = getString(R.string.common_name) + "  ";
+            String oNameLabel = getString(R.string.organization) + "  ";
+            String uNameLabel = getString(R.string.organizational_unit) + "  ";
+            String startDateLabel = getString(R.string.start_date) + "  ";
+            String endDateLabel = getString(R.string.end_date) + "  ";
+
+            // Get the current website SSL certificate.
+            SslCertificate sslCertificate = MainWebViewActivity.sslCertificate;
+
+            // Extract the individual pieces of information from the current website SSL certificate if it is not null.
+            if (sslCertificate != null) {
+                currentSslIssuedToCNameString = sslCertificate.getIssuedTo().getCName();
+                currentSslIssuedToONameString = sslCertificate.getIssuedTo().getOName();
+                currentSslIssuedToUNameString = sslCertificate.getIssuedTo().getUName();
+                currentSslIssuedByCNameString = sslCertificate.getIssuedBy().getCName();
+                currentSslIssuedByONameString = sslCertificate.getIssuedBy().getOName();
+                currentSslIssuedByUNameString = sslCertificate.getIssuedBy().getUName();
+                currentSslStartDate = sslCertificate.getValidNotBeforeDate();
+                currentSslEndDate = sslCertificate.getValidNotAfterDate();
+            } else {
+                // Initialize the current website SSL certificate variables with blank information.
+                currentSslIssuedToCNameString = "";
+                currentSslIssuedToONameString = "";
+                currentSslIssuedToUNameString = "";
+                currentSslIssuedByCNameString = "";
+                currentSslIssuedByONameString = "";
+                currentSslIssuedByUNameString = "";
+            }
+
+            // Initialize the `SpannableStringBuilders`.
+            SpannableStringBuilder issuedToCNameStringBuilder;
+            SpannableStringBuilder issuedToONameStringBuilder;
+            SpannableStringBuilder issuedToUNameStringBuilder;
+            SpannableStringBuilder issuedByCNameStringBuilder;
+            SpannableStringBuilder issuedByONameStringBuilder;
+            SpannableStringBuilder issuedByUNameStringBuilder;
+            SpannableStringBuilder startDateStringBuilder;
+            SpannableStringBuilder endDateStringBuilder;
+
+            // Setup the `SpannableStringBuilders` for each tab.
+            if (position == 0) {  // Setup the current SSL certificate tab.
+                issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedToCNameString);
+                issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedToONameString);
+                issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedToUNameString);
+                issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedByCNameString);
+                issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedByONameString);
+                issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedByUNameString);
+
+                // Set the dates if they aren't `null`.
+                if (currentSslStartDate == null) {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
+                } else {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate));
+                }
+
+                if (currentSslEndDate == null) {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
+                } else {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate));
+                }
+            } else {  // Setup the pinned SSL certificate tab.
+                issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToCNameString);
+                issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToONameString);
+                issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToUNameString);
+                issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByCNameString);
+                issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByONameString);
+                issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByUNameString);
+
+                // Set the dates if they aren't `null`.
+                if (MainWebViewActivity.pinnedDomainSslStartDate == null) {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
+                } else {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(MainWebViewActivity.pinnedDomainSslStartDate));
+                }
+
+                if (MainWebViewActivity.pinnedDomainSslEndDate == null) {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
+                } else {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(MainWebViewActivity.pinnedDomainSslEndDate));
+                }
+            }
+
+            // Create a red `ForegroundColorSpan`.  We have to use the deprecated `getColor` until API >= 23.
+            @SuppressWarnings("deprecation") ForegroundColorSpan redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+
+            // Create a blue `ForegroundColorSpan`.
+            ForegroundColorSpan blueColorSpan;
+
+            // Set `blueColorSpan` according to the theme.  We have to use the deprecated `getColor()` until API >= 23.
+            if (MainWebViewActivity.darkTheme) {
+                //noinspection deprecation
+                blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400));
+            } else {
+                //noinspection deprecation
+                blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
+            }
+
+            // Configure the spans to display conflicting information in red.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            if (currentSslIssuedToCNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToCNameString)) {
+                issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedToONameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToONameString)) {
+                issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedToUNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToUNameString)) {
+                issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedByCNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByCNameString)) {
+                issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedByONameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByONameString)) {
+                issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedByUNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByUNameString)) {
+                issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if ((currentSslStartDate != null) && (MainWebViewActivity.pinnedDomainSslStartDate != null) && currentSslStartDate.equals(MainWebViewActivity.pinnedDomainSslStartDate)) {
+                startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if ((currentSslEndDate != null) && (MainWebViewActivity.pinnedDomainSslEndDate != null) && currentSslEndDate.equals(MainWebViewActivity.pinnedDomainSslEndDate)) {
+                endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            // Display the strings.
+            issuedToCNameTextView.setText(issuedToCNameStringBuilder);
+            issuedToONameTextView.setText(issuedToONameStringBuilder);
+            issuedToUNameTextView.setText(issuedToUNameStringBuilder);
+            issuedByCNameTextView.setText(issuedByCNameStringBuilder);
+            issuedByONameTextView.setText(issuedByONameStringBuilder);
+            issuedByUNameTextView.setText(issuedByUNameStringBuilder);
+            startDateTextView.setText(startDateStringBuilder);
+            endDateTextView.setText(endDateStringBuilder);
+
+            // Display the tab.
+            container.addView(tabViewGroup);
+
+            // Make it so.
+            return tabViewGroup;
+        }
+    }
+}
index f0d5a69..e966b2e 100644 (file)
@@ -138,11 +138,19 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment {
         // Use `AlertDialog.Builder` to create the `AlertDialog`.
         AlertDialog.Builder dialogBuilder;
 
-        // Set the style according to the theme.
+        // Set the style and icon according to the theme.
         if (MainWebViewActivity.darkTheme) {
+            // Set the style.
             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
+
+            // Set the icon.
+            dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark);
         } else {
+            // Set the style.
             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
+
+            // Set the icon.
+            dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light);
         }
 
         // Set the title.
index b47dec3..2a76854 100644 (file)
@@ -22,12 +22,19 @@ package com.stoutner.privacybrowser.fragments;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.database.Cursor;
+import android.net.http.SslCertificate;
 import android.os.Build;
 import android.os.Bundle;
 // We have to use `android.support.v4.app.Fragment` until minimum API >= 23.  Otherwise we cannot call `getContext()`.
 import android.preference.PreferenceManager;
 import android.support.v4.app.Fragment;
+import android.text.Editable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextWatcher;
+import android.text.style.ForegroundColorSpan;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -38,6 +45,7 @@ import android.widget.CompoundButton;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.RadioButton;
 import android.widget.Spinner;
 import android.widget.Switch;
 import android.widget.TextView;
@@ -46,11 +54,15 @@ import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.activities.MainWebViewActivity;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
 
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
 public class DomainSettingsFragment extends Fragment {
     // `DATABASE_ID` is used by activities calling this fragment.
     public static final String DATABASE_ID = "database_id";
 
-    // `databaseId` is public statis so it can be accessed from `DomainsActivity`. It is also used in `onCreate()` and `onCreateView()`.
+    // `databaseId` is public static so it can be accessed from `DomainsActivity`. It is also used in `onCreate()` and `onCreateView()`.
     public static int databaseId;
 
     @Override
@@ -68,11 +80,21 @@ public class DomainSettingsFragment extends Fragment {
         // Inflate `domain_settings_fragment`.  `false` does not attach it to the root `container`.
         View domainSettingsView = inflater.inflate(R.layout.domain_settings_fragment, container, false);
 
-        // Get a handle for the `Context`.
+        // Get a handle for the `Context` and the `Resources`.
         Context context = getContext();
+        final Resources resources = getResources();
+
+        // Get a handle for the shared preference.
+        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+
+        // Store the default user agent string values.
+        final String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
+        final String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
+        String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
+        boolean defaultDisplayWebpageImagesBoolean = sharedPreferences.getBoolean("display_website_images", true);
 
         // Get handles for the views in the fragment.
-        EditText domainNameEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_name_edittext);
+        final EditText domainNameEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_name_edittext);
         Switch javaScriptEnabledSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_javascript_switch);
         final ImageView javaScriptImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_javascript_imageview);
         Switch firstPartyCookiesEnabledSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_first_party_cookies_switch);
@@ -88,8 +110,43 @@ public class DomainSettingsFragment extends Fragment {
         final TextView userAgentTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_user_agent_textview);
         final EditText customUserAgentEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_custom_user_agent_edittext);
         Spinner fontSizeSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_font_size_spinner);
+        final TextView fontSizeTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_font_size_textview);
         final ImageView displayWebpageImagesImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_imageview);
         Spinner displayWebpageImagesSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_spinner);
+        final TextView displayImagesTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_textview);
+        final ImageView pinnedSslCertificateImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_pinned_ssl_certificate_imageview);
+        Switch pinnedSslCertificateSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
+        final LinearLayout savedSslCertificateLinearLayout = (LinearLayout) domainSettingsView.findViewById(R.id.saved_ssl_certificate_linearlayout);
+        final RadioButton savedSslCertificateRadioButton = (RadioButton) domainSettingsView.findViewById(R.id.saved_ssl_certificate_radiobutton);
+        final TextView savedSslCertificateIssuedToCNameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_cname);
+        TextView savedSslCertificateIssuedToONameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_oname);
+        TextView savedSslCertificateIssuedToUNameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_uname);
+        TextView savedSslCertificateIssuedByCNameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_cname);
+        TextView savedSslCertificateIssuedByONameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_oname);
+        TextView savedSslCertificateIssuedByUNameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_uname);
+        TextView savedSslCertificateStartDateTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_start_date);
+        TextView savedSslCertificateEndDateTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_end_date);
+        final LinearLayout currentWebsiteCertificateLinearLayout = (LinearLayout) domainSettingsView.findViewById(R.id.current_website_certificate_linearlayout);
+        final RadioButton currentWebsiteCertificateRadioButton = (RadioButton) domainSettingsView.findViewById(R.id.current_website_certificate_radiobutton);
+        final TextView currentWebsiteCertificateIssuedToCNameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_cname);
+        TextView currentWebsiteCertificateIssuedToONameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_oname);
+        TextView currentWebsiteCertificateIssuedToUNameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_uname);
+        TextView currentWebsiteCertificateIssuedByCNameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_cname);
+        TextView currentWebsiteCertificateIssuedByONameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_oname);
+        TextView currentWebsiteCertificateIssuedByUNameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_uname);
+        TextView currentWebsiteCertificateStartDateTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_start_date);
+        TextView currentWebsiteCertificateEndDateTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_end_date);
+        final TextView noCurrentWebsiteCertificateTextView = (TextView) domainSettingsView.findViewById(R.id.no_current_website_certificate);
+
+        // Setup the SSL certificate labels.
+        final String cNameLabel = getString(R.string.common_name) + "  ";
+        String oNameLabel = getString(R.string.organization) + "  ";
+        String uNameLabel = getString(R.string.organizational_unit) + "  ";
+        String startDateLabel = getString(R.string.start_date) + "  ";
+        String endDateLabel = getString(R.string.end_date) + "  ";
+
+        // Get the current website SSL certificate
+        final SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
 
         // Initialize the database handler.  The two `nulls` do not specify the database name or a `CursorFactory`.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
         DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(context, null, null, 0);
@@ -108,6 +165,26 @@ public class DomainSettingsFragment extends Fragment {
         final String currentUserAgentString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
         int fontSizeInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
         int displayImagesInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+        int pinnedSslCertificateInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE));
+        final String savedSslCertificateIssuedToCNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
+        String savedSslCertificateIssuedToONameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
+        String savedSslCertificateIssuedToUNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
+        String savedSslCertificateIssuedByCNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
+        String savedSslCertificateIssuedByONameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
+        String savedSslCertificateIssuedByUNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+
+        // Initialize the saved SSL certificate date variables.
+        Date savedSslCertificateStartDate = null;
+        Date savedSslCertificateEndDate = null;
+
+        // Only get the saved SSL certificate dates from the cursor if they are not set to `0`.
+        if (domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) != 0) {
+            savedSslCertificateStartDate = new Date(domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+        }
+
+        if (domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) != 0) {
+            savedSslCertificateEndDate = new Date(domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+        }
 
         // Create `ArrayAdapters` for the `Spinners`and their `entry values`.
         ArrayAdapter<CharSequence> userAgentArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_user_agent_entries, R.layout.spinner_item);
@@ -126,30 +203,127 @@ public class DomainSettingsFragment extends Fragment {
         fontSizeSpinner.setAdapter(fontSizeArrayAdapter);
         displayWebpageImagesSpinner.setAdapter(displayImagesArrayAdapter);
 
+        // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text.
+        SpannableStringBuilder savedSslCertificateIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslCertificateIssuedToCNameString);
+        SpannableStringBuilder savedSslCertificateIssuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + savedSslCertificateIssuedToONameString);
+        SpannableStringBuilder savedSslCertificateIssuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + savedSslCertificateIssuedToUNameString);
+        SpannableStringBuilder savedSslCertificateIssuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslCertificateIssuedByCNameString);
+        SpannableStringBuilder savedSslCertificateIssuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + savedSslCertificateIssuedByONameString);
+        SpannableStringBuilder savedSslCertificateIssuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + savedSslCertificateIssuedByUNameString);
+
+        // Initialize the `SpannableStringBuilders` for the SSL certificate dates.
+        SpannableStringBuilder savedSslCertificateStartDateStringBuilder;
+        SpannableStringBuilder savedSslCertificateEndDateStringBuilder;
+
+        // Leave the SSL certificate dates empty if they are `null`.
+        if (savedSslCertificateStartDate == null) {
+            savedSslCertificateStartDateStringBuilder = new SpannableStringBuilder(startDateLabel);
+        } else {
+            savedSslCertificateStartDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslCertificateStartDate));
+        }
+
+        if (savedSslCertificateEndDate == null) {
+            savedSslCertificateEndDateStringBuilder = new SpannableStringBuilder(endDateLabel);
+        } else {
+            savedSslCertificateEndDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslCertificateEndDate));
+        }
+
+        // Create a red `ForegroundColorSpan`.  We have to use the deprecated `getColor` until API >= 23.
+        final ForegroundColorSpan redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+
+        // Create a blue `ForegroundColorSpan`.
+        final ForegroundColorSpan blueColorSpan;
+
+        // Set `blueColorSpan` according to the theme.  We have to use the deprecated `getColor()` until API >= 23.
+        if (MainWebViewActivity.darkTheme) {
+            //noinspection deprecation
+            blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400));
+        } else {
+            //noinspection deprecation
+            blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
+        }
+
         // Set the domain name from the the database cursor.
         domainNameEditText.setText(domainNameString);
 
+        // Update the certificates' `Common Name` color when the domain name text changes.
+        domainNameEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // Do nothing.
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                // Get the new domain name.
+                String newDomainName = domainNameEditText.getText().toString();
+
+                // Check the saved SSL certificate against the new domain name.
+                boolean savedSslCertificateMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, savedSslCertificateIssuedToCNameString);
+
+                // Create a `SpannableStringBuilder` for the saved certificate `Common Name`.
+                SpannableStringBuilder savedSslCertificateCommonNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslCertificateIssuedToCNameString);
+
+                // Format the saved certificate `Common Name` color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+                if (savedSslCertificateMatchesNewDomainName) {
+                    savedSslCertificateCommonNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslCertificateCommonNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                } else {
+                    savedSslCertificateCommonNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), savedSslCertificateCommonNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+
+                // Update `savedSslCertificateIssuedToCNameTextView`.
+                savedSslCertificateIssuedToCNameTextView.setText(savedSslCertificateCommonNameStringBuilder);
+
+                // Update the current website certificate if it exists.
+                if (currentWebsiteSslCertificate != null) {
+                    // Get the current website certificate `Common Name`.
+                    String currentWebsiteCertificateCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName();
+
+                    // Check the current website certificate against the new domain name.
+                    boolean currentWebsiteCertificateMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, currentWebsiteCertificateCommonName);
+
+                    // Create a `SpannableStringBuilder` for the current website certificate `Common Name`.
+                    SpannableStringBuilder currentWebsiteCertificateCommonNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentWebsiteCertificateCommonName);
+
+                    // Format the current certificate `Common Name` color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+                    if (currentWebsiteCertificateMatchesNewDomainName) {
+                        currentWebsiteCertificateCommonNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentWebsiteCertificateCommonNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    } else {
+                        currentWebsiteCertificateCommonNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), currentWebsiteCertificateCommonNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                    }
+
+                    // Update `currentWebsiteCertificateIssuedToCNameTextView`.
+                    currentWebsiteCertificateIssuedToCNameTextView.setText(currentWebsiteCertificateCommonNameStringBuilder);
+                }
+            }
+        });
+
         // Set the JavaScript status.
         if (javaScriptEnabledInt == 1) {  // JavaScript is enabled.
             javaScriptEnabledSwitch.setChecked(true);
-            javaScriptImageView.setImageDrawable(getResources().getDrawable(R.drawable.javascript_enabled));
+            javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled));
         } else {  // JavaScript is disabled.
             javaScriptEnabledSwitch.setChecked(false);
-            javaScriptImageView.setImageDrawable(getResources().getDrawable(R.drawable.privacy_mode));
+            javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode));
         }
 
         // Set the first-party cookies status.  Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
         if (firstPartyCookiesEnabledInt == 1) {  // First-party cookies are enabled.
             firstPartyCookiesEnabledSwitch.setChecked(true);
-            firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_enabled));
+            firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_enabled));
         } else {  // First-party cookies are disabled.
             firstPartyCookiesEnabledSwitch.setChecked(false);
 
             // Set the icon according to the theme.
             if (MainWebViewActivity.darkTheme) {
-                firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark));
+                firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark));
             } else {
-                firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light));
+                firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light));
             }
         }
 
@@ -160,15 +334,15 @@ public class DomainSettingsFragment extends Fragment {
                 // Set the third-party cookies status.  Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
                 if (thirdPartyCookiesEnabledInt == 1) {  // Both first-party and third-party cookies are enabled.
                     thirdPartyCookiesEnabledSwitch.setChecked(true);
-                    thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_warning));
+                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning));
                 } else {  // First party cookies are enabled but third-party cookies are disabled.
                     thirdPartyCookiesEnabledSwitch.setChecked(false);
 
                     // Set the icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark));
+                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark));
                     } else {
-                        thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light));
+                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light));
                     }
                 }
             } else {  // First-party cookies are disabled.
@@ -184,9 +358,9 @@ public class DomainSettingsFragment extends Fragment {
 
                 // Set the icon according to the theme.
                 if (MainWebViewActivity.darkTheme) {
-                    thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_ghosted_dark));
+                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_dark));
                 } else {
-                    thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_ghosted_light));
+                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_light));
                 }
             }
         } else {  // Third-party cookies cannot be configured for API <= 21.
@@ -199,16 +373,16 @@ public class DomainSettingsFragment extends Fragment {
             // Set the DOM storage status.  Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
             if (domStorageEnabledInt == 1) {  // Both JavaScript and DOM storage are enabled.
                 domStorageEnabledSwitch.setChecked(true);
-                domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_enabled));
+                domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled));
             } else {  // JavaScript is enabled but DOM storage is disabled.
                 // Set the DOM storage switch to off.
                 domStorageEnabledSwitch.setChecked(false);
 
                 // Set the icon according to the theme.
                 if (MainWebViewActivity.darkTheme) {
-                    domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_dark));
+                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_dark));
                 } else {
-                    domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_light));
+                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_light));
                 }
             }
         } else {  // JavaScript is disabled.
@@ -224,25 +398,25 @@ public class DomainSettingsFragment extends Fragment {
 
             // Set the icon according to the theme.
             if (MainWebViewActivity.darkTheme) {
-                domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_ghosted_dark));
+                domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_dark));
             } else {
-                domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_ghosted_light));
+                domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_light));
             }
         }
 
         // Set the form data status.  Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
         if (formDataEnabledInt == 1) {  // Form data is enabled.
             formDataEnabledSwitch.setChecked(true);
-            formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_enabled));
+            formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_enabled));
         } else {  // Form data is disabled.
             // Set the form data switch to off.
             formDataEnabledSwitch.setChecked(false);
 
             // Set the icon according to the theme.
             if (MainWebViewActivity.darkTheme) {
-                formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_disabled_dark));
+                formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_dark));
             } else {
-                formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_disabled_light));
+                formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_light));
             }
         }
 
@@ -252,13 +426,6 @@ public class DomainSettingsFragment extends Fragment {
         WebView bareWebView = (WebView) bareWebViewLayout.findViewById(R.id.bare_webview);
         final String webViewDefaultUserAgentString = bareWebView.getSettings().getUserAgentString();
 
-        // Get a handle for the shared preference.
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-
-        // Store the default user agent string values.
-        final String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
-        final String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
-
         // Get the position of the user agent in `userAgentEntryValuesArrayAdapter`.
         int userAgentArrayPosition = userAgentEntryValuesArrayAdapter.getPosition(currentUserAgentString);
 
@@ -319,47 +486,251 @@ public class DomainSettingsFragment extends Fragment {
         int fontSizeArrayPosition = fontSizeEntryValuesArrayAdapter.getPosition(String.valueOf(fontSizeInt));
         fontSizeSpinner.setSelection(fontSizeArrayPosition);
 
+        // Set the default font size text.
+        int defaultFontSizeArrayPosition = fontSizeEntryValuesArrayAdapter.getPosition(defaultFontSizeString);
+        fontSizeTextView.setText(fontSizeArrayAdapter.getItem(defaultFontSizeArrayPosition));
+
+        // Set the display options for `fontSizeTextView`.
+        if (fontSizeArrayPosition == 0) {  // System default font size is selected.  Display `fontSizeTextView`.
+            fontSizeTextView.setVisibility(View.VISIBLE);
+        } else {  // A custom font size is specified.  Hide `fontSizeTextView`.
+            fontSizeTextView.setVisibility(View.GONE);
+        }
+
         // Set the selected display website images mode.
         displayWebpageImagesSpinner.setSelection(displayImagesInt);
 
-        // Set the display website images icon.
+        // Set the default display images text.
+        if (defaultDisplayWebpageImagesBoolean) {
+            displayImagesTextView.setText(displayImagesArrayAdapter.getItem(1));
+        } else {
+            displayImagesTextView.setText(displayImagesArrayAdapter.getItem(2));
+        }
+
+        // Set the display website images icon and `TextView` settings.
         switch (displayImagesInt) {
             case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
                 if (MainWebViewActivity.displayWebpageImagesBoolean) {
                     // Set the icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_dark));
+                        displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark));
                     } else {
-                        displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_light));
+                        displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light));
                     }
                 } else {
                     // Set the icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_dark));
+                        displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark));
                     } else {
-                        displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_light));
+                        displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_light));
                     }
                 }
+
+                // Show `displayImagesTextView`.
+                displayImagesTextView.setVisibility(View.VISIBLE);
                 break;
 
             case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
                 // Set the icon according to the theme.
                 if (MainWebViewActivity.darkTheme) {
-                    displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_dark));
+                    displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark));
                 } else {
-                    displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_light));
+                    displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light));
                 }
+
+                // Hide `displayImagesTextView`.
+                displayImagesTextView.setVisibility(View.GONE);
                 break;
 
             case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
                 // Set the icon according to the theme.
                 if (MainWebViewActivity.darkTheme) {
-                    displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_dark));
+                    displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark));
                 } else {
-                    displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_light));
+                    displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_light));
                 }
+
+                // Hide `displayImagesTextView`.
+                displayImagesTextView.setVisibility(View.GONE);
                 break;
         }
+        
+        // Set the pinned SSL certificate icon.
+        if (pinnedSslCertificateInt == 1) {  // Pinned SSL certificate is enabled.
+            // Check the switch.
+            pinnedSslCertificateSwitch.setChecked(true);
+
+            // Set the icon according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_dark));
+            } else {
+                pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_light));
+            }
+        } else {  // Pinned SSL certificate is disabled.
+            // Uncheck the switch.
+            pinnedSslCertificateSwitch.setChecked(false);
+
+            // Set the icon according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_dark));
+            } else {
+                pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_light));
+            }
+        }
+
+        // Store the current date.
+        Date currentDate = Calendar.getInstance().getTime();
+
+        // Setup the `StringBuilders` to display the general certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+        savedSslCertificateIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), savedSslCertificateIssuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        savedSslCertificateIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), savedSslCertificateIssuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        savedSslCertificateIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslCertificateIssuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        savedSslCertificateIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), savedSslCertificateIssuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        savedSslCertificateIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), savedSslCertificateIssuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+        // Check the certificate `Common Name` against the domain name.
+        boolean savedSSlCertificateCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, savedSslCertificateIssuedToCNameString);
+
+        // Format the `issuedToCommonName` color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+        if (savedSSlCertificateCommonNameMatchesDomainName) {
+            savedSslCertificateIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslCertificateIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        } else {
+            savedSslCertificateIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), savedSslCertificateIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        }
+
+        //  Format the start date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+        if ((savedSslCertificateStartDate != null) && savedSslCertificateStartDate.after(currentDate)) {  // The certificate start date is in the future.
+            savedSslCertificateStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), savedSslCertificateStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        } else {  // The certificate start date is in the past.
+            savedSslCertificateStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), savedSslCertificateStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        }
+
+        // Format the end date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+        if ((savedSslCertificateEndDate != null) && savedSslCertificateEndDate.before(currentDate)) {  // The certificate end date is in the past.
+            savedSslCertificateEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), savedSslCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        } else {  // The certificate end date is in the future.
+            savedSslCertificateEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), savedSslCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        }
+
+        // Display the current website SSL certificate strings.
+        savedSslCertificateIssuedToCNameTextView.setText(savedSslCertificateIssuedToCNameStringBuilder);
+        savedSslCertificateIssuedToONameTextView.setText(savedSslCertificateIssuedToONameStringBuilder);
+        savedSslCertificateIssuedToUNameTextView.setText(savedSslCertificateIssuedToUNameStringBuilder);
+        savedSslCertificateIssuedByCNameTextView.setText(savedSslCertificateIssuedByCNameStringBuilder);
+        savedSslCertificateIssuedByONameTextView.setText(savedSslCertificateIssuedByONameStringBuilder);
+        savedSslCertificateIssuedByUNameTextView.setText(savedSslCertificateIssuedByUNameStringBuilder);
+        savedSslCertificateStartDateTextView.setText(savedSslCertificateStartDateStringBuilder);
+        savedSslCertificateEndDateTextView.setText(savedSslCertificateEndDateStringBuilder);
+
+        // Populate the current website SSL certificate if there is one.
+        if (currentWebsiteSslCertificate != null) {
+            // Get the strings from the SSL certificate.
+            String currentWebsiteCertificateIssuedToCNameString = currentWebsiteSslCertificate.getIssuedTo().getCName();
+            String currentWebsiteCertificateIssuedToONameString = currentWebsiteSslCertificate.getIssuedTo().getOName();
+            String currentWebsiteCertificateIssuedToUNameString = currentWebsiteSslCertificate.getIssuedTo().getUName();
+            String currentWebsiteCertificateIssuedByCNameString = currentWebsiteSslCertificate.getIssuedBy().getCName();
+            String currentWebsiteCertificateIssuedByONameString = currentWebsiteSslCertificate.getIssuedBy().getOName();
+            String currentWebsiteCertificateIssuedByUNameString = currentWebsiteSslCertificate.getIssuedBy().getUName();
+            Date currentWebsiteCertificateStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
+            Date currentWebsiteCertificateEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
+
+            // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text.
+            SpannableStringBuilder currentWebsiteCertificateIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentWebsiteCertificateIssuedToCNameString);
+            SpannableStringBuilder currentWebsiteCertificateIssuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentWebsiteCertificateIssuedToONameString);
+            SpannableStringBuilder currentWebsiteCertificateIssuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentWebsiteCertificateIssuedToUNameString);
+            SpannableStringBuilder currentWebsiteCertificateIssuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentWebsiteCertificateIssuedByCNameString);
+            SpannableStringBuilder currentWebsiteCertificateIssuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentWebsiteCertificateIssuedByONameString);
+            SpannableStringBuilder currentWebsiteCertificateIssuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentWebsiteCertificateIssuedByUNameString);
+            SpannableStringBuilder currentWebsiteCertificateStartDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentWebsiteCertificateStartDate));
+            SpannableStringBuilder currentWebsiteCertificateEndDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentWebsiteCertificateEndDate));
+
+            // Setup the `StringBuilders` to display the general certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            currentWebsiteCertificateIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), currentWebsiteCertificateIssuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            currentWebsiteCertificateIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), currentWebsiteCertificateIssuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            currentWebsiteCertificateIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentWebsiteCertificateIssuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            currentWebsiteCertificateIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), currentWebsiteCertificateIssuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            currentWebsiteCertificateIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), currentWebsiteCertificateIssuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+            // Check the certificate `Common Name` against the domain name.
+            boolean currentWebsiteCertificateCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, currentWebsiteCertificateIssuedToCNameString);
+
+            // Format the `issuedToCommonName` color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            if (currentWebsiteCertificateCommonNameMatchesDomainName) {
+                currentWebsiteCertificateIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentWebsiteCertificateIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                currentWebsiteCertificateIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), currentWebsiteCertificateIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            //  Format the start date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            if (currentWebsiteCertificateStartDate.after(currentDate)) {  // The certificate start date is in the future.
+                currentWebsiteCertificateStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), currentWebsiteCertificateStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {  // The certificate start date is in the past.
+                currentWebsiteCertificateStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), currentWebsiteCertificateStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            // Format the end date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            if (currentWebsiteCertificateEndDate.before(currentDate)) {  // The certificate end date is in the past.
+                currentWebsiteCertificateEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), currentWebsiteCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {  // The certificate end date is in the future.
+                currentWebsiteCertificateEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), currentWebsiteCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            // Display the current website SSL certificate strings.
+            currentWebsiteCertificateIssuedToCNameTextView.setText(currentWebsiteCertificateIssuedToCNameStringBuilder);
+            currentWebsiteCertificateIssuedToONameTextView.setText(currentWebsiteCertificateIssuedToONameStringBuilder);
+            currentWebsiteCertificateIssuedToUNameTextView.setText(currentWebsiteCertificateIssuedToUNameStringBuilder);
+            currentWebsiteCertificateIssuedByCNameTextView.setText(currentWebsiteCertificateIssuedByCNameStringBuilder);
+            currentWebsiteCertificateIssuedByONameTextView.setText(currentWebsiteCertificateIssuedByONameStringBuilder);
+            currentWebsiteCertificateIssuedByUNameTextView.setText(currentWebsiteCertificateIssuedByUNameStringBuilder);
+            currentWebsiteCertificateStartDateTextView.setText(currentWebsiteCertificateStartDateStringBuilder);
+            currentWebsiteCertificateEndDateTextView.setText(currentWebsiteCertificateEndDateStringBuilder);
+        }
+
+        // Set the initial display status for the SSL certificates.
+        if (pinnedSslCertificateSwitch.isChecked()) {
+            // Set the visibility of the saved SSL certificate.
+            if (savedSslCertificateIssuedToCNameString == null) {
+                savedSslCertificateLinearLayout.setVisibility(View.GONE);
+            } else {
+                savedSslCertificateLinearLayout.setVisibility(View.VISIBLE);
+            }
+
+            // Set the visibility of the current website SSL certificate.
+            if (currentWebsiteSslCertificate == null) {
+                // Hide the SSL certificate.
+                currentWebsiteCertificateLinearLayout.setVisibility(View.GONE);
+
+                // Show the instruction.
+                noCurrentWebsiteCertificateTextView.setVisibility(View.VISIBLE);
+            } else {
+                // Show the SSL certificate.
+                currentWebsiteCertificateLinearLayout.setVisibility(View.VISIBLE);
+
+                // Hide the instruction.
+                noCurrentWebsiteCertificateTextView.setVisibility(View.GONE);
+            }
+
+            // Set the status of the radio buttons.
+            if (savedSslCertificateLinearLayout.getVisibility() == View.VISIBLE) {  // The saved SSL certificate is displayed.
+                savedSslCertificateRadioButton.setChecked(true);
+                currentWebsiteCertificateRadioButton.setChecked(false);
+            } else if (currentWebsiteCertificateLinearLayout.getVisibility() == View.VISIBLE) {  // The saved SSL certificate is hidden but the current website SSL certificate is visible.
+                currentWebsiteCertificateRadioButton.setChecked(true);
+                savedSslCertificateRadioButton.setChecked(false);
+            } else {  // Neither SSL certificate is visible.
+                savedSslCertificateRadioButton.setChecked(false);
+                currentWebsiteCertificateRadioButton.setChecked(false);
+            }
+        } else {  // `pinnedSslCertificateSwitch` is not checked.
+            // Hide the SSl certificates and instructions.
+            savedSslCertificateLinearLayout.setVisibility(View.GONE);
+            currentWebsiteCertificateLinearLayout.setVisibility(View.GONE);
+            noCurrentWebsiteCertificateTextView.setVisibility(View.GONE);
+
+            // Uncheck the radio buttons.
+            savedSslCertificateRadioButton.setChecked(false);
+            currentWebsiteCertificateRadioButton.setChecked(false);
+        }
 
 
         // Set the `javaScriptEnabledSwitch` `OnCheckedChangeListener()`.
@@ -368,34 +739,34 @@ public class DomainSettingsFragment extends Fragment {
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 if (isChecked) {  // JavaScript is enabled.
                     // Update the JavaScript icon.
-                    javaScriptImageView.setImageDrawable(getResources().getDrawable(R.drawable.javascript_enabled));
+                    javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled));
 
                     // Enable the DOM storage `Switch`.
                     domStorageEnabledSwitch.setEnabled(true);
 
                     // Update the DOM storage icon.
                     if (domStorageEnabledSwitch.isChecked()) {  // DOM storage is enabled.
-                        domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_enabled));
+                        domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled));
                     } else {  // DOM storage is disabled.
                         // Set the icon according to the theme.
                         if (MainWebViewActivity.darkTheme) {
-                            domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_dark));
+                            domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_dark));
                         } else {
-                            domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_light));
+                            domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_light));
                         }
                     }
                 } else {  // JavaScript is disabled.
                     // Update the JavaScript icon.
-                    javaScriptImageView.setImageDrawable(getResources().getDrawable(R.drawable.privacy_mode));
+                    javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode));
 
                     // Disable the DOM storage `Switch`.
                     domStorageEnabledSwitch.setEnabled(false);
 
                     // Set the DOM storage icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_ghosted_dark));
+                        domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_dark));
                     } else {
-                        domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_ghosted_light));
+                        domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_light));
                     }
                 }
             }
@@ -407,28 +778,28 @@ public class DomainSettingsFragment extends Fragment {
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 if (isChecked) {  // First-party cookies are enabled.
                     // Update the first-party cookies icon.
-                    firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_enabled));
+                    firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_enabled));
 
                     // Enable the third-party cookies `Switch`.
                     thirdPartyCookiesEnabledSwitch.setEnabled(true);
 
                     // Update the third-party cookies icon.
                     if (thirdPartyCookiesEnabledSwitch.isChecked()) {  // Third-party cookies are enabled.
-                        thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_warning));
+                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning));
                     } else {  // Third-party cookies are disabled.
                         // Set the third-party cookies icon according to the theme.
                         if (MainWebViewActivity.darkTheme) {
-                            thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark));
+                            thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark));
                         } else {
-                            thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light));
+                            thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light));
                         }
                     }
                 } else {  // First-party cookies are disabled.
                     // Update the first-party cookies icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark));
+                        firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark));
                     } else {
-                        firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light));
+                        firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light));
                     }
 
                     // Disable the third-party cookies `Switch`.
@@ -436,9 +807,9 @@ public class DomainSettingsFragment extends Fragment {
 
                     // Set the third-party cookies icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_ghosted_dark));
+                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_dark));
                     } else {
-                        thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_ghosted_light));
+                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_light));
                     }
                 }
             }
@@ -450,13 +821,13 @@ public class DomainSettingsFragment extends Fragment {
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 // Update the icon.
                 if (isChecked) {
-                    thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_warning));
+                    thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning));
                 } else {
                     // Update the third-party cookies icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark));
+                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark));
                     } else {
-                        thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light));
+                        thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light));
                     }
                 }
             }
@@ -468,13 +839,13 @@ public class DomainSettingsFragment extends Fragment {
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 // Update the icon.
                 if (isChecked) {
-                    domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_enabled));
+                    domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled));
                 } else {
                     // Set the icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_dark));
+                        domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_dark));
                     } else {
-                        domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_light));
+                        domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_light));
                     }
                 }
             }
@@ -486,13 +857,13 @@ public class DomainSettingsFragment extends Fragment {
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 // Update the icon.
                 if (isChecked) {
-                    formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_enabled));
+                    formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_enabled));
                 } else {
                     // Set the icon according to the theme.
                     if (MainWebViewActivity.darkTheme) {
-                        formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_disabled_dark));
+                        formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_dark));
                     } else {
-                        formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_disabled_light));
+                        formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_light));
                     }
                 }
             }
@@ -503,7 +874,7 @@ public class DomainSettingsFragment extends Fragment {
             @Override
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                 // Store the new user agent string.
-                String newUserAgentString = getResources().getStringArray(R.array.domain_settings_user_agent_entry_values)[position];
+                String newUserAgentString = resources.getStringArray(R.array.domain_settings_user_agent_entry_values)[position];
 
                 // Set the new user agent.
                 switch (newUserAgentString) {
@@ -566,46 +937,73 @@ public class DomainSettingsFragment extends Fragment {
             }
         });
 
-        // Set the `displayImagesSwitch` `onItemClickListener()`.
+        // Set the `fontSizeSpinner` `onItemSelectedListener()`.
+        fontSizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                // Update the display options for `fontSizeTextView`.
+                if (position == 0) {  // System default font size has been selected.  Display `fontSizeTextView`.
+                    fontSizeTextView.setVisibility(View.VISIBLE);
+                } else {  // A custom font size has been selected.  Hide `fontSizeTextView`.
+                    fontSizeTextView.setVisibility(View.GONE);
+                }
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+                // Do nothing.
+            }
+        });
+
+        // Set the `displayWebpageImagesSpinner` `onItemSelectedListener()`.
         displayWebpageImagesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
             @Override
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-                // Update the icon.
+                // Update the icon and the visibility of `displayImagesTextView`.
                 switch (position) {
                     case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
                         if (MainWebViewActivity.displayWebpageImagesBoolean) {
                             // Set the icon according to the theme.
                             if (MainWebViewActivity.darkTheme) {
-                                displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_dark));
+                                displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark));
                             } else {
-                                displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_light));
+                                displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light));
                             }
                         } else {
                             // Set the icon according to the theme.
                             if (MainWebViewActivity.darkTheme) {
-                                displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_dark));
+                                displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark));
                             } else {
-                                displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_light));
+                                displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_light));
                             }
                         }
+
+                        // Show `displayImagesTextView`.
+                        displayImagesTextView.setVisibility(View.VISIBLE);
                         break;
 
                     case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED:
                         // Set the icon according to the theme.
                         if (MainWebViewActivity.darkTheme) {
-                            displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_dark));
+                            displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark));
                         } else {
-                            displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_light));
+                            displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light));
                         }
+
+                        // Hide `displayImagesTextView`.
+                        displayImagesTextView.setVisibility(View.GONE);
                         break;
 
                     case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED:
                         // Set the icon according to the theme.
                         if (MainWebViewActivity.darkTheme) {
-                            displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_dark));
+                            displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark));
                         } else {
-                            displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_light));
+                            displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_light));
                         }
+
+                        // Hide `displayImagesTextView`.
+                        displayImagesTextView.setVisibility(View.GONE);
                         break;
                 }
             }
@@ -615,7 +1013,181 @@ public class DomainSettingsFragment extends Fragment {
                 // Do nothing.
             }
         });
+        
+        // Set the `pinnedSSLCertificateSwitch` `onCheckedChangeListener()`.
+        pinnedSslCertificateSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                // Update the icon
+                if (isChecked) {  // Pinned SSL certificate is enabled.
+                    // Set the icon according to the theme.
+                    if (MainWebViewActivity.darkTheme) {
+                        pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_dark));
+                    } else {
+                        pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_light));
+                    }
+
+                    // Update the visibility of the saved SSL certificate.
+                    if (savedSslCertificateIssuedToCNameString == null) {
+                        savedSslCertificateLinearLayout.setVisibility(View.GONE);
+                    } else {
+                        savedSslCertificateLinearLayout.setVisibility(View.VISIBLE);
+                    }
+
+                    // Update the visibility of the current website SSL certificate.
+                    if (currentWebsiteSslCertificate == null) {
+                        // Hide the SSL certificate.
+                        currentWebsiteCertificateLinearLayout.setVisibility(View.GONE);
+
+                        // Show the instruction.
+                        noCurrentWebsiteCertificateTextView.setVisibility(View.VISIBLE);
+                    } else {
+                        // Show the SSL certificate.
+                        currentWebsiteCertificateLinearLayout.setVisibility(View.VISIBLE);
+
+                        // Hide the instruction.
+                        noCurrentWebsiteCertificateTextView.setVisibility(View.GONE);
+                    }
+
+                    // Set the status of the radio buttons.
+                    if (savedSslCertificateLinearLayout.getVisibility() == View.VISIBLE) {  // The saved SSL certificate is displayed.
+                        savedSslCertificateRadioButton.setChecked(true);
+                        currentWebsiteCertificateRadioButton.setChecked(false);
+                    } else if (currentWebsiteCertificateLinearLayout.getVisibility() == View.VISIBLE) {  // The saved SSL certificate is hidden but the current website SSL certificate is visible.
+                        currentWebsiteCertificateRadioButton.setChecked(true);
+                        savedSslCertificateRadioButton.setChecked(false);
+                    } else {  // Neither SSL certificate is visible.
+                        savedSslCertificateRadioButton.setChecked(false);
+                        currentWebsiteCertificateRadioButton.setChecked(false);
+                    }
+                } else {  // Pinned SSL certificate is disabled.
+                    // Set the icon according to the theme.
+                    if (MainWebViewActivity.darkTheme) {
+                        pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_dark));
+                    } else {
+                        pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_light));
+                    }
+
+                    // Hide the SSl certificates and instructions.
+                    savedSslCertificateLinearLayout.setVisibility(View.GONE);
+                    currentWebsiteCertificateLinearLayout.setVisibility(View.GONE);
+                    noCurrentWebsiteCertificateTextView.setVisibility(View.GONE);
+
+                    // Uncheck the radio buttons.
+                    savedSslCertificateRadioButton.setChecked(false);
+                    currentWebsiteCertificateRadioButton.setChecked(false);
+                }
+            }
+        });
+
+        savedSslCertificateLinearLayout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                savedSslCertificateRadioButton.setChecked(true);
+                currentWebsiteCertificateRadioButton.setChecked(false);
+            }
+        });
+
+        savedSslCertificateRadioButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                savedSslCertificateRadioButton.setChecked(true);
+                currentWebsiteCertificateRadioButton.setChecked(false);
+            }
+        });
+
+        currentWebsiteCertificateLinearLayout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                currentWebsiteCertificateRadioButton.setChecked(true);
+                savedSslCertificateRadioButton.setChecked(false);
+            }
+        });
+
+        currentWebsiteCertificateRadioButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                currentWebsiteCertificateRadioButton.setChecked(true);
+                savedSslCertificateRadioButton.setChecked(false);
+            }
+        });
 
         return domainSettingsView;
     }
+
+    private boolean checkDomainNameAgainstCertificate(String domainName, String certificateCommonName) {
+        // Initialize `domainNamesMatch`.
+        boolean domainNamesMatch = false;
+
+        // Check if the domains match.
+        if (domainName.equals(certificateCommonName)) {
+            domainNamesMatch = true;
+        }
+
+        // Check various wildcard permutations if `domainName` and `certificateCommonName` are not empty.  `noinspection ConstantCondition` removes Android Studio's incorrect lint warning that `domainName` can never be `null`.
+        //noinspection ConstantConditions
+        if ((domainName != null) && (certificateCommonName != null)) {
+            // If `domainName` starts with a wildcard, check the base domain against all the subdomains of `certificateCommonName`.
+            if (!domainNamesMatch && domainName.startsWith("*.") && (domainName.length() > 2)) {
+                // Remove the initial `*.`.
+                String baseDomainName = domainName.substring(2);
+
+                // Setup a copy of `certificateCommonName` to test subdomains.
+                String certificateCommonNameSubdomain = certificateCommonName;
+
+                // Check all the subdomains in `certificateCommonNameSubdomains` against `baseDomainName`.
+                while (!domainNamesMatch && certificateCommonNameSubdomain.contains(".")) {  // Stop checking if we know that `domainNamesMatch` is `true` or if we run out of  `.`.
+                    // Test the `certificateCommonNameSubdomain` against `baseDomainName`.
+                    if (certificateCommonNameSubdomain.equals(baseDomainName)) {
+                        domainNamesMatch = true;
+                    }
+
+                    // Strip out the lowest subdomain of `certificateCommonNameSubdomain`.
+                    try {
+                        certificateCommonNameSubdomain = certificateCommonNameSubdomain.substring(certificateCommonNameSubdomain.indexOf(".") + 1);
+                    } catch (IndexOutOfBoundsException e) {  // `certificateCommonNameSubdomain` ends with `.`.
+                        certificateCommonNameSubdomain = "";
+                    }
+                }
+            }
+
+            // If `certificateCommonName` starts with a wildcard, check the base common name against all the subdomains of `domainName`.
+            if (!domainNamesMatch && certificateCommonName.startsWith("*.") && (certificateCommonName.length() > 2)) {
+                // Remove the initial `*.`.
+                String baseCertificateCommonName = certificateCommonName.substring(2);
+
+                // Setup a copy of `domainName` to test subdomains.
+                String domainNameSubdomain = domainName;
+
+                // Check all the subdomains in `domainNameSubdomain` against `baseCertificateCommonName`.
+                while (!domainNamesMatch && domainNameSubdomain.contains(".") && (domainNameSubdomain.length() > 2)) {
+                    // Test the `domainNameSubdomain` against `baseCertificateCommonName`.
+                    if (domainNameSubdomain.equals(baseCertificateCommonName)) {
+                        domainNamesMatch = true;
+                    }
+
+                    // Strip out the lowest subdomain of `domainNameSubdomain`.
+                    try {
+                        domainNameSubdomain = domainNameSubdomain.substring(domainNameSubdomain.indexOf(".") + 1);
+                    } catch (IndexOutOfBoundsException e) { // `domainNameSubdomain` ends with `.`.
+                        domainNameSubdomain = "";
+                    }
+                }
+            }
+
+            // If both names start with a wildcard, check if the root of one contains the root of the other.
+            if (!domainNamesMatch && domainName.startsWith("*.") && (domainName.length() > 2) && certificateCommonName.startsWith("*.") && (certificateCommonName.length() > 2)) {
+                // Remove the wildcards.
+                String rootDomainName = domainName.substring(2);
+                String rootCertificateCommonName = certificateCommonName.substring(2);
+
+                // Check if one name ends with the contents of the other.  If so, there will be overlap in the their wildcard subdomains.
+                if (rootDomainName.endsWith(rootCertificateCommonName) || rootCertificateCommonName.endsWith(rootDomainName)) {
+                    domainNamesMatch = true;
+                }
+            }
+        }
+
+        return domainNamesMatch;
+    }
 }
index 553c107..34349d7 100644 (file)
@@ -19,6 +19,7 @@
 
 package com.stoutner.privacybrowser.fragments;
 
+import android.net.http.SslCertificate;
 import android.os.Bundle;
 import android.support.design.widget.FloatingActionButton;
 // We have to use `android.support.v4.app.Fragment` until minimum API >= 23.  Otherwise we cannot call `getContext()`.
@@ -30,6 +31,7 @@ import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.EditText;
 import android.widget.ListView;
+import android.widget.RadioButton;
 import android.widget.Spinner;
 import android.widget.Switch;
 
@@ -78,6 +80,9 @@ public class DomainsListFragment extends Fragment {
                     EditText customUserAgentEditText = (EditText) domainSettingsFragmentView.findViewById(R.id.domain_settings_custom_user_agent_edittext);
                     Spinner fontSizeSpinner = (Spinner) domainSettingsFragmentView.findViewById(R.id.domain_settings_font_size_spinner);
                     Spinner displayWebpageImagesSpinner = (Spinner) domainSettingsFragmentView.findViewById(R.id.domain_settings_display_webpage_images_spinner);
+                    Switch pinnedSslCertificateSwitch = (Switch) domainSettingsFragmentView.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
+                    RadioButton savedSslCertificateRadioButton = (RadioButton) domainSettingsFragmentView.findViewById(R.id.saved_ssl_certificate_radiobutton);
+                    RadioButton currentWebsiteCertificateRadioButton = (RadioButton) domainSettingsFragmentView.findViewById(R.id.current_website_certificate_radiobutton);
 
                     // Extract the data for the domain settings.
                     String domainNameString = domainNameEditText.getText().toString();
@@ -89,6 +94,7 @@ public class DomainsListFragment extends Fragment {
                     int userAgentPositionInt = userAgentSpinner.getSelectedItemPosition();
                     int fontSizePositionInt = fontSizeSpinner.getSelectedItemPosition();
                     int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
+                    boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
 
                     // Get the data for the `Spinners` from the entry values string arrays.
                     String userAgentString = getResources().getStringArray(R.array.domain_settings_user_agent_entry_values)[userAgentPositionInt];
@@ -101,8 +107,33 @@ public class DomainsListFragment extends Fragment {
                     }
 
                     // Save the domain settings.
-                    domainsDatabaseHelper.saveDomain(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean, userAgentString, fontSizeInt,
-                            displayWebpageImagesInt);
+                    if (savedSslCertificateRadioButton.isChecked()) {  // The current certificate is being used.
+                        // Update the database except for the certificate.
+                        domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean,
+                                formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate);
+                    } else if (currentWebsiteCertificateRadioButton.isChecked()) {  // The certificate is being updated with the current website certificate.
+                        // Get the current website SSL certificate.
+                        SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
+
+                        // Store the values from the SSL certificate.
+                        String issuedToCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName();
+                        String issuedToOrganization = currentWebsiteSslCertificate.getIssuedTo().getOName();
+                        String issuedToOrganizationalUnit = currentWebsiteSslCertificate.getIssuedTo().getUName();
+                        String issuedByCommonName = currentWebsiteSslCertificate.getIssuedBy().getCName();
+                        String issuedByOrganization = currentWebsiteSslCertificate.getIssuedBy().getOName();
+                        String issuedByOrganizationalUnit = currentWebsiteSslCertificate.getIssuedBy().getUName();
+                        long startDateLong = currentWebsiteSslCertificate.getValidNotBeforeDate().getTime();
+                        long endDateLong = currentWebsiteSslCertificate.getValidNotAfterDate().getTime();
+
+                        // Update the database.
+                        domainsDatabaseHelper.updateDomainWithCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean,
+                                formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization,
+                                issuedByOrganizationalUnit, startDateLong, endDateLong);
+                    } else {  // No certificate is selected.
+                        // Update the database, with PINNED_SSL_CERTIFICATE set to false.
+                        domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean,
+                                formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, false);
+                    }
                 }
 
                 // Store the new `currentDomainDatabaseId`, converting it from `long` to `int` to match the format of the domains database.
index bb1a9c5..3100d69 100644 (file)
@@ -26,7 +26,7 @@ import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 
 public class DomainsDatabaseHelper extends SQLiteOpenHelper {
-    private static final int SCHEMA_VERSION = 2;
+    private static final int SCHEMA_VERSION = 3;
     private static final String DOMAINS_DATABASE = "domains.db";
     private static final String DOMAINS_TABLE = "domains";
 
@@ -40,6 +40,15 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
     public static final String USER_AGENT = "useragent";
     public static final String FONT_SIZE = "fontsize";
     public static final String DISPLAY_IMAGES = "displayimages";
+    public static final String PINNED_SSL_CERTIFICATE = "pinnedsslcertificate";
+    public static final String SSL_ISSUED_TO_COMMON_NAME = "sslissuedtocommonname";
+    public static final String SSL_ISSUED_TO_ORGANIZATION = "sslissuedtoorganization";
+    public static final String SSL_ISSUED_TO_ORGANIZATIONAL_UNIT = "sslissuedtoorganizationalunit";
+    public static final String SSL_ISSUED_BY_COMMON_NAME = "sslissuedbycommonname";
+    public static final String SSL_ISSUED_BY_ORGANIZATION = "sslissuedbyorganization";
+    public static final String SSL_ISSUED_BY_ORGANIZATIONAL_UNIT = "sslissuedbyorganizationalunit";
+    public static final String SSL_START_DATE = "sslstartdate";
+    public static final String SSL_END_DATE = "sslenddate";
 
     public static final int DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT = 0;
     public static final int DISPLAY_WEBPAGE_IMAGES_ENABLED = 1;
@@ -53,7 +62,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
     @Override
     public void onCreate(SQLiteDatabase domainsDatabase) {
         // Setup the SQL string to create the `domains` table.
-        final String CREATE_DOMAINS_TABLE = "CREATE TABLE " + DOMAINS_TABLE + " (" +
+        String CREATE_DOMAINS_TABLE = "CREATE TABLE " + DOMAINS_TABLE + " (" +
                 _ID + " INTEGER PRIMARY KEY, " +
                 DOMAIN_NAME + " TEXT, " +
                 ENABLE_JAVASCRIPT + " BOOLEAN, " +
@@ -63,9 +72,18 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
                 ENABLE_FORM_DATA + " BOOLEAN, " +
                 USER_AGENT + " TEXT, " +
                 FONT_SIZE + " INTEGER, " +
-                DISPLAY_IMAGES + " INTEGER);";
-
-        // Create the `domains` table if it doesn't exist.
+                DISPLAY_IMAGES + " INTEGER, " +
+                PINNED_SSL_CERTIFICATE + " BOOLEAN, " +
+                SSL_ISSUED_TO_COMMON_NAME + " TEXT, " +
+                SSL_ISSUED_TO_ORGANIZATION + " TEXT, " +
+                SSL_ISSUED_TO_ORGANIZATIONAL_UNIT + " TEXT, " +
+                SSL_ISSUED_BY_COMMON_NAME + " TEXT, " +
+                SSL_ISSUED_BY_ORGANIZATION + " TEXT, " +
+                SSL_ISSUED_BY_ORGANIZATIONAL_UNIT + " TEXT, " +
+                SSL_START_DATE + " INTEGER, " +
+                SSL_END_DATE + " INTEGER);";
+
+        // Make it so.
         domainsDatabase.execSQL(CREATE_DOMAINS_TABLE);
     }
 
@@ -77,6 +95,19 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
             case 1:
                 // Add the `DISPLAY_IMAGES` column.
                 domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + DISPLAY_IMAGES + " INTEGER");
+
+            // Upgrade from `SCHEMA_VERSION` 2.
+            case 2:
+                //  Add the SSL certificate columns.
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + PINNED_SSL_CERTIFICATE + " BOOLEAN");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_TO_COMMON_NAME + " TEXT");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_TO_ORGANIZATION + " TEXT");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_TO_ORGANIZATIONAL_UNIT + " TEXT");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_BY_COMMON_NAME + " TEXT");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_BY_ORGANIZATION + " TEXT");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_BY_ORGANIZATIONAL_UNIT + " TEXT");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_START_DATE + " INTEGER");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_END_DATE + " INTEGER");
         }
     }
 
@@ -158,8 +189,36 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
         return newDomainDatabaseId;
     }
 
-    public void saveDomain(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled, String userAgent, int fontSize,
-                           int displayImages) {
+    public void updateDomainExceptCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled, String userAgent, int fontSize,
+                                              int displayImages, boolean pinnedSslCertificate) {
+        // Store the domain data in a `ContentValues`.
+        ContentValues domainContentValues = new ContentValues();
+
+        // Add entries for each field in the database.
+        domainContentValues.put(DOMAIN_NAME, domainName);
+        domainContentValues.put(ENABLE_JAVASCRIPT, javaScriptEnabled);
+        domainContentValues.put(ENABLE_FIRST_PARTY_COOKIES, firstPartyCookiesEnabled);
+        domainContentValues.put(ENABLE_THIRD_PARTY_COOKIES, thirdPartyCookiesEnabled);
+        domainContentValues.put(ENABLE_DOM_STORAGE, domStorageEnabled);
+        domainContentValues.put(ENABLE_FORM_DATA, formDataEnabled);
+        domainContentValues.put(USER_AGENT, userAgent);
+        domainContentValues.put(FONT_SIZE, fontSize);
+        domainContentValues.put(DISPLAY_IMAGES, displayImages);
+        domainContentValues.put(PINNED_SSL_CERTIFICATE, pinnedSslCertificate);
+
+        // Get a writable database handle.
+        SQLiteDatabase domainsDatabase = this.getWritableDatabase();
+
+        // Update the row for `databaseId`.  The last argument is `null` because there are no `whereArgs`.
+        domainsDatabase.update(DOMAINS_TABLE, domainContentValues, _ID + " = " + databaseId, null);
+
+        // Close the database handle.
+        domainsDatabase.close();
+    }
+
+    public void updateDomainWithCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled, String userAgent, int fontSize,
+                                            int displayImages, boolean pinnedSslCertificate, String sslIssuedToCommonName, String sslIssuedToOrganization, String sslIssuedToOrganizationalUnit, String sslIssuedByCommonName, String sslIssuedByOrganization,
+                                            String sslIssuedByOrganizationalUnit, long sslStartDate, long sslEndDate) {
         // Store the domain data in a `ContentValues`.
         ContentValues domainContentValues = new ContentValues();
 
@@ -173,6 +232,40 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
         domainContentValues.put(USER_AGENT, userAgent);
         domainContentValues.put(FONT_SIZE, fontSize);
         domainContentValues.put(DISPLAY_IMAGES, displayImages);
+        domainContentValues.put(PINNED_SSL_CERTIFICATE, pinnedSslCertificate);
+        domainContentValues.put(SSL_ISSUED_TO_COMMON_NAME, sslIssuedToCommonName);
+        domainContentValues.put(SSL_ISSUED_TO_ORGANIZATION, sslIssuedToOrganization);
+        domainContentValues.put(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, sslIssuedToOrganizationalUnit);
+        domainContentValues.put(SSL_ISSUED_BY_COMMON_NAME, sslIssuedByCommonName);
+        domainContentValues.put(SSL_ISSUED_BY_ORGANIZATION, sslIssuedByOrganization);
+        domainContentValues.put(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, sslIssuedByOrganizationalUnit);
+        domainContentValues.put(SSL_START_DATE, sslStartDate);
+        domainContentValues.put(SSL_END_DATE, sslEndDate);
+
+        // Get a writable database handle.
+        SQLiteDatabase domainsDatabase = this.getWritableDatabase();
+
+        // Update the row for `databaseId`.  The last argument is `null` because there are no `whereArgs`.
+        domainsDatabase.update(DOMAINS_TABLE, domainContentValues, _ID + " = " + databaseId, null);
+
+        // Close the database handle.
+        domainsDatabase.close();
+    }
+
+    public void updateCertificate(int databaseId, String sslIssuedToCommonName, String sslIssuedToOrganization, String sslIssuedToOrganizationalUnit, String sslIssuedByCommonName, String sslIssuedByOrganization, String sslIssuedByOrganizationalUnit,
+                                  long sslStartDate, long sslEndDate) {
+        // Store the domain data in a `ContentValues`.
+        ContentValues domainContentValues = new ContentValues();
+
+        // Add entries for each field in the certificate.
+        domainContentValues.put(SSL_ISSUED_TO_COMMON_NAME, sslIssuedToCommonName);
+        domainContentValues.put(SSL_ISSUED_TO_ORGANIZATION, sslIssuedToOrganization);
+        domainContentValues.put(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, sslIssuedToOrganizationalUnit);
+        domainContentValues.put(SSL_ISSUED_BY_COMMON_NAME, sslIssuedByCommonName);
+        domainContentValues.put(SSL_ISSUED_BY_ORGANIZATION, sslIssuedByOrganization);
+        domainContentValues.put(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, sslIssuedByOrganizationalUnit);
+        domainContentValues.put(SSL_START_DATE, sslStartDate);
+        domainContentValues.put(SSL_END_DATE, sslEndDate);
 
         // Get a writable database handle.
         SQLiteDatabase domainsDatabase = this.getWritableDatabase();
diff --git a/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml b/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml
new file mode 100644 (file)
index 0000000..3f6998c
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `ssl_certificate_enabled_dark.xml` comes from the Android Material icon set, where it is called `ic_vpn_lock`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <path
+        android:fillColor="#FF9E9E9E"
+        android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5L17,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L23,5c0,-0.55 -0.45,-1 -1,-1zM21.2,4h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L21.2,4zM18.92,12c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L7,13v-2h2c0.55,0 1,-0.45 1,-1L10,8h2c1.1,0 2,-0.9 2,-2L14,3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 10,-4.48 10,-10c0,-0.34 -0.02,-0.67 -0.05,-1h-2.03zM10,20.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L8,16v1c0,1.1 0.9,2 2,2v1.93z" />
+</vector>
diff --git a/app/src/main/res/drawable/ssl_certificate_disabled_light.xml b/app/src/main/res/drawable/ssl_certificate_disabled_light.xml
new file mode 100644 (file)
index 0000000..c801069
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `ssl_certificate_enabled_light.xml` comes from the Android Material icon set, where it is called `ic_vpn_lock`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <path
+        android:fillColor="#88000000"
+        android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5L17,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L23,5c0,-0.55 -0.45,-1 -1,-1zM21.2,4h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L21.2,4zM18.92,12c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L7,13v-2h2c0.55,0 1,-0.45 1,-1L10,8h2c1.1,0 2,-0.9 2,-2L14,3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 10,-4.48 10,-10c0,-0.34 -0.02,-0.67 -0.05,-1h-2.03zM10,20.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L8,16v1c0,1.1 0.9,2 2,2v1.93z" />
+</vector>
diff --git a/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml b/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml
new file mode 100644 (file)
index 0000000..ba20d43
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `ssl_certificate_enabled_dark.xml` comes from the Android Material icon set, where it is called `ic_vpn_lock`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <path
+        android:fillColor="#FF1E88E5"
+        android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5L17,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L23,5c0,-0.55 -0.45,-1 -1,-1zM21.2,4h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L21.2,4zM18.92,12c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L7,13v-2h2c0.55,0 1,-0.45 1,-1L10,8h2c1.1,0 2,-0.9 2,-2L14,3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 10,-4.48 10,-10c0,-0.34 -0.02,-0.67 -0.05,-1h-2.03zM10,20.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L8,16v1c0,1.1 0.9,2 2,2v1.93z" />
+</vector>
diff --git a/app/src/main/res/drawable/ssl_certificate_enabled_light.xml b/app/src/main/res/drawable/ssl_certificate_enabled_light.xml
new file mode 100644 (file)
index 0000000..9fc00fe
--- /dev/null
@@ -0,0 +1,18 @@
+<!-- `ssl_certificate_enabled_light.xml` comes from the Android Material icon set, where it is called `ic_vpn_lock`.  It is released under the Apache License 2.0. -->
+
+<!-- `tools:ignore="VectorRaster"` removes the lint warning about `android:autoMirrored="true"` not applying to API < 21. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:autoMirrored="true"
+    tools:ignore="VectorRaster" >
+
+    <!-- We have to use a hard coded color until API >= 21.  Then we can use `@color`. -->
+    <path
+        android:fillColor="#FF1565C0"
+        android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5L17,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L23,5c0,-0.55 -0.45,-1 -1,-1zM21.2,4h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L21.2,4zM18.92,12c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L7,13v-2h2c0.55,0 1,-0.45 1,-1L10,8h2c1.1,0 2,-0.9 2,-2L14,3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 10,-4.48 10,-10c0,-0.34 -0.02,-0.67 -0.05,-1h-2.03zM10,20.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L8,16v1c0,1.1 0.9,2 2,2v1.93z" />
+</vector>
index 9e43e76..4c9c4ae 100644 (file)
@@ -63,8 +63,8 @@
         <!-- `android:layout_weight="1"` makes `about_viewpager` fill the rest of the screen. -->
         <android.support.v4.view.ViewPager
             android:id="@+id/about_viewpager"
-            android:layout_width="match_parent"
             android:layout_height="0dp"
+            android:layout_width="match_parent"
             android:layout_weight="1" />
     </LinearLayout>
 </android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
index 176c542..dff393c 100644 (file)
@@ -50,6 +50,5 @@
         android:text="@string/domain_name_already_exists"
         android:textColor="?attr/redText"
         android:layout_marginStart="8dp"
-        android:layout_marginEnd="8dp"
-        android:layout_marginBottom="12dp" />
+        android:layout_marginEnd="8dp" />
 </LinearLayout>
\ No newline at end of file
index 1d7bf63..b27a1c4 100644 (file)
@@ -92,6 +92,7 @@
                 android:id="@+id/domain_settings_javascript_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
+                android:layout_marginTop="1dp"
                 android:layout_marginEnd="10dp"
                 android:layout_gravity="center_vertical"
                 tools:ignore="contentDescription" />
         <LinearLayout
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
-            android:orientation="horizontal" >
+            android:orientation="vertical"
+            android:layout_marginTop="14dp"
+            android:layout_marginBottom="14dp" >
 
-            <ImageView
+            <LinearLayout
                 android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:layout_marginTop="1dp"
-                android:layout_marginEnd="10dp"
-                android:layout_gravity="center_vertical"
-                android:src="@drawable/font_size_light"
-                android:tint="?attr/iconTintColor"
-                android:contentDescription="@string/font_size" />
+                android:layout_width="match_parent"
+                android:orientation="horizontal" >
 
-            <Spinner
-                android:id="@+id/domain_settings_font_size_spinner"
-                android:layout_height="wrap_content"
+                <ImageView
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:layout_marginTop="1dp"
+                    android:layout_marginEnd="10dp"
+                    android:layout_gravity="center_vertical"
+                    android:src="@drawable/font_size_light"
+                    android:tint="?attr/iconTintColor"
+                    android:contentDescription="@string/font_size" />
+
+                <Spinner
+                    android:id="@+id/domain_settings_font_size_spinner"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent" />
+            </LinearLayout>
+
+            <TextView
+                android:id="@+id/domain_settings_font_size_textview"
+                android:layout_height="match_parent"
                 android:layout_width="match_parent"
-                android:layout_marginTop="14dp"
-                android:layout_marginBottom="14dp" />
+                android:layout_marginStart="45dp"
+                android:layout_marginEnd="36dp"
+                android:textSize="13sp" />
+
         </LinearLayout>
 
         <!-- Display Images. -->
         <LinearLayout
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
-            android:orientation="horizontal" >
+            android:orientation="vertical"
+            android:layout_marginTop="14dp"
+            android:layout_marginBottom="14dp" >
 
-            <ImageView
-                android:id="@+id/domain_settings_display_webpage_images_imageview"
+            <LinearLayout
                 android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:layout_marginTop="1dp"
-                android:layout_marginEnd="10dp"
-                android:layout_gravity="center_vertical"
-                android:contentDescription="@string/display_webpage_images" />
+                android:layout_width="match_parent"
+                android:orientation="horizontal" >
+
+                <ImageView
+                    android:id="@+id/domain_settings_display_webpage_images_imageview"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:layout_marginTop="1dp"
+                    android:layout_marginEnd="10dp"
+                    android:layout_gravity="center_vertical"
+                    android:contentDescription="@string/display_webpage_images" />
+
+                <Spinner
+                    android:id="@+id/domain_settings_display_webpage_images_spinner"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent" />
+            </LinearLayout>
+
+            <TextView
+                android:id="@+id/domain_settings_display_webpage_images_textview"
+                android:layout_height="match_parent"
+                android:layout_width="match_parent"
+                android:layout_marginStart="45dp"
+                android:layout_marginEnd="36dp"
+                android:textSize="13sp" />
+        </LinearLayout>
+
+        <!-- Pinned SSL Certificate -->
+        <LinearLayout
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="vertical"
+            android:layout_marginTop="18dp"
+            android:layout_marginBottom="32dp" >
 
-            <Spinner
-                android:id="@+id/domain_settings_display_webpage_images_spinner"
+            <!-- Switch -->
+            <LinearLayout
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
-                android:layout_marginTop="14dp"
-                android:layout_marginBottom="14dp" />
+                android:orientation="horizontal" >
+
+                <ImageView
+                    android:id="@+id/domain_settings_pinned_ssl_certificate_imageview"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:layout_marginTop="1dp"
+                    android:layout_marginEnd="10dp"
+                    android:layout_gravity="center_vertical"
+                    tools:ignore="contentDescription" />
+
+                <Switch
+                    android:id="@+id/domain_settings_pinned_ssl_certificate_switch"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:layout_marginStart="8dp"
+                    android:text="@string/pinned_ssl_certificate"
+                    android:textColor="?attr/primaryTextColorSelector"
+                    android:textSize="18sp" />
+            </LinearLayout>
+
+            <!-- Saved Certificate -->
+            <LinearLayout
+                android:id="@+id/saved_ssl_certificate_linearlayout"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:orientation="vertical"
+                android:layout_marginTop="20dp"
+                android:layout_marginBottom="12dp"
+                android:layout_marginStart="10dp"
+                android:layout_marginEnd="10dp" >
+
+                <RadioButton
+                    android:id="@+id/saved_ssl_certificate_radiobutton"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:text="@string/saved_ssl_certificate"
+                    android:textSize="17sp"
+                    android:textAllCaps="true"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:layout_marginStart="32dp"
+                    android:orientation="vertical" >
+
+                    <!-- Saved Certificate Issued To. -->
+                    <TextView
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:text="@string/issued_to"
+                        android:textAllCaps="true"
+                        android:textStyle="bold"
+                        android:textColor="?attr/sslTitle" />
+
+                    <TextView
+                        android:id="@+id/saved_ssl_certificate_issued_to_cname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/saved_ssl_certificate_issued_to_oname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/saved_ssl_certificate_issued_to_uname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"/>
+
+                    <!-- Saved Certificate Issued By. -->
+                    <TextView
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:layout_marginTop="15dp"
+                        android:text="@string/issued_by"
+                        android:textAllCaps="true"
+                        android:textStyle="bold"
+                        android:textColor="?attr/sslTitle"/>
+
+                    <TextView
+                        android:id="@+id/saved_ssl_certificate_issued_by_cname"
+
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/saved_ssl_certificate_issued_by_oname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/saved_ssl_certificate_issued_by_uname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <!-- Saved Certificate Valid Dates. -->
+                    <TextView
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:layout_marginTop="15dp"
+                        android:text="@string/valid_dates"
+                        android:textAllCaps="true"
+                        android:textStyle="bold"
+                        android:textColor="?attr/sslTitle"/>
+
+                    <TextView
+                        android:id="@+id/saved_ssl_certificate_start_date"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/saved_ssl_certificate_end_date"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+                </LinearLayout>
+            </LinearLayout>
+
+            <!-- Current Website Certificate -->
+            <LinearLayout
+                android:id="@+id/current_website_certificate_linearlayout"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:orientation="vertical"
+                android:layout_marginTop="20dp"
+                android:layout_marginBottom="8dp"
+                android:layout_marginStart="10dp"
+                android:layout_marginEnd="10dp" >
+
+                <RadioButton
+                    android:id="@+id/current_website_certificate_radiobutton"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:text="@string/current_website_ssl_certificate"
+                    android:textSize="17sp"
+                    android:textAllCaps="true"
+                    android:textStyle="bold" />
+
+                <LinearLayout
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:layout_marginStart="32dp"
+                    android:orientation="vertical" >
+
+                    <!-- Current Website Certificate Issued To. -->
+                    <TextView
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:text="@string/issued_to"
+                        android:textAllCaps="true"
+                        android:textStyle="bold"
+                        android:textColor="?attr/sslTitle" />
+
+                    <TextView
+                        android:id="@+id/current_website_certificate_issued_to_cname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/current_website_certificate_issued_to_oname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/current_website_certificate_issued_to_uname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <!-- Current Website Certificate Issued By. -->
+                    <TextView
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:layout_marginTop="15dp"
+                        android:text="@string/issued_by"
+                        android:textAllCaps="true"
+                        android:textStyle="bold"
+                        android:textColor="?attr/sslTitle" />
+
+                    <TextView
+                        android:id="@+id/current_website_certificate_issued_by_cname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/current_website_certificate_issued_by_oname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/current_website_certificate_issued_by_uname"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <!-- Current Website Certificate Valid Dates. -->
+                    <TextView
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:layout_marginTop="15dp"
+                        android:text="@string/valid_dates"
+                        android:textAllCaps="true"
+                        android:textStyle="bold"
+                        android:textColor="?attr/sslTitle" />
+
+                    <TextView
+                        android:id="@+id/current_website_certificate_start_date"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+
+                    <TextView
+                        android:id="@+id/current_website_certificate_end_date"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent" />
+                </LinearLayout>
+            </LinearLayout>
+
+            <!-- Load An Encrypted Website Instructions. -->
+            <TextView
+                android:id="@+id/no_current_website_certificate"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:layout_marginTop="10dp"
+                android:layout_marginStart="40dp"
+                android:layout_marginEnd="40dp"
+                android:gravity="center_horizontal"
+                android:text="@string/load_an_encrypted_website" />
         </LinearLayout>
     </LinearLayout>
 </ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/pinned_ssl_certificate_mismatch_linearlayout.xml b/app/src/main/res/layout/pinned_ssl_certificate_mismatch_linearlayout.xml
new file mode 100644 (file)
index 0000000..115008a
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:android.support.design="http://schemas.android.com/apk/res-auto"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:orientation="vertical">
+
+    <android.support.design.widget.TabLayout
+        android:id="@+id/pinned_ssl_certificate_mismatch_tablayout"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android.support.design:tabMode="scrollable"
+        android:theme="?attr/dialogTabLayoutTheme" />
+
+    <com.stoutner.privacybrowser.definitions.WrapVerticalContentViewPager
+        android:id="@+id/pinned_ssl_certificate_mismatch_viewpager"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/pinned_ssl_certificate_mismatch_scrollview.xml b/app/src/main/res/layout/pinned_ssl_certificate_mismatch_scrollview.xml
new file mode 100644 (file)
index 0000000..2782c49
--- /dev/null
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content" >
+
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:padding="10dp"
+        android:orientation="vertical" >
+
+        <!-- Issued To. -->
+        <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/issued_to"
+            android:textAllCaps="true"
+            android:textStyle="bold"
+            android:textColor="?attr/sslTitle" />
+
+        <TextView
+            android:id="@+id/issued_to_cname"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+        <TextView
+            android:id="@+id/issued_to_oname"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+        <TextView
+            android:id="@+id/issued_to_uname"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+
+        <!-- Issued By. -->
+        <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginTop="15dp"
+            android:text="@string/issued_by"
+            android:textAllCaps="true"
+            android:textStyle="bold"
+            android:textColor="?attr/sslTitle" />
+
+        <TextView
+            android:id="@+id/issued_by_cname"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+        <TextView
+            android:id="@+id/issued_by_oname"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+        <TextView
+            android:id="@+id/issued_by_uname"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+
+        <!-- Valid Dates. -->
+        <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginTop="15dp"
+            android:text="@string/valid_dates"
+            android:textAllCaps="true"
+            android:textStyle="bold"
+            android:textColor="?attr/sslTitle" />
+
+        <TextView
+            android:id="@+id/start_date"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+
+        <TextView
+            android:id="@+id/end_date"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
index d33ab53..f1ce5c1 100644 (file)
@@ -31,8 +31,8 @@
 
         <!-- Issued To. -->
         <TextView
-            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
             android:text="@string/issued_to"
             android:textAllCaps="true"
             android:textStyle="bold"
         <TextView
             android:id="@+id/issued_to_uname"
             android:layout_height="wrap_content"
-            android:layout_width="wrap_content"/>
+            android:layout_width="wrap_content" />
 
 
         <!-- Issued By. -->
         <TextView
-            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
             android:layout_marginTop="15dp"
             android:text="@string/issued_by"
             android:textAllCaps="true"
             android:textStyle="bold"
-            android:textColor="?attr/sslTitle"/>
+            android:textColor="?attr/sslTitle" />
 
         <TextView
             android:id="@+id/issued_by_cname"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
 
         <TextView
             android:id="@+id/issued_by_oname"
@@ -88,7 +88,7 @@
             android:text="@string/valid_dates"
             android:textAllCaps="true"
             android:textStyle="bold"
-            android:textColor="?attr/sslTitle"/>
+            android:textColor="?attr/sslTitle" />
 
         <TextView
             android:id="@+id/start_date"
index 9a3e922..04fdac9 100644 (file)
     <string name="domains">Dominios</string>
     <string name="domain_settings">Configuración de dominio</string>
     <string name="add_domain">Añadir dominio</string>
+    <string name="domain_name_already_exists">El nombre de dominio ya existe</string>
     <string name="add">Añadir</string>
     <string name="domain_name">Nombre de dominio</string>
     <string name="domain_settings_saved">Configuración de dominio guardada</string>
index a2a5617..c94d144 100644 (file)
     <string name="domains">Domini</string>
     <string name="domain_settings">Impostazioni Domini</string>
     <string name="add_domain">Aggiungi Dominio</string>
+    <string name="domain_name_already_exists">Il nome del Dominio è già esistente</string>
     <string name="add">Aggiungi</string>
     <string name="domain_name">Nome del Dominio</string>
     <string name="domain_settings_saved">Impostazioni Domini Salvate</string>
index a756278..6e4ad1f 100644 (file)
     <attr name="appBarTextTheme" format="reference" />
     <attr name="popupsTheme" format="reference" />
     <attr name="tabLayoutTheme" format="reference" />
+    <attr name="dialogTabLayoutTheme" format="reference" />
 
     <attr name="aboutTitle" format="reference" />
     <attr name="aboutText" format="reference" />
     <attr name="sslTitle" format="reference" />
+    <attr name="sslHeader" format="reference" />
     <attr name="urlHistoryText" format="reference" />
     <attr name="primaryTextColorSelector" format="reference" />
     <attr name="redText" format="reference" />
index ff782d9..821407a 100644 (file)
@@ -32,6 +32,7 @@
     <color name="blue_500">#FF2196F3</color>
     <color name="blue_600">#FF1E88E5</color>
     <color name="blue_700">#FF1976D2</color>
+    <color name="blue_700_50">#881976D2</color>
     <color name="blue_800">#FF1565C0</color>
     <color name="blue_900">#FF0D47A1</color>
     <color name="blue_1000">#FF082B61</color>
index c825d38..4bb849c 100644 (file)
     <string name="url">URL</string>
     <string name="url_label">URL:</string>
 
+    <!-- Pinned SSL Certificate Mismatch. -->
+    <string name="update_ssl">Update SSL</string>
+    <string name="ssl_certificate_mismatch">SSL Certificate Mismatch</string>
+    <string name="current_ssl">Current SSL</string>
+    <string name="pinned_ssl">Pinned SSL</string>
+
     <!-- MainWebViewActivity Navigation Drawer. -->
     <string name="navigation_drawer">Navigation Drawer</string>
     <string name="navigation">Navigation</string>
         <item>Images enabled</item>
         <item>Images disabled</item>
     </string-array>
+    <string name="pinned_ssl_certificate">Pinned SSL certificate</string>
+    <string name="saved_ssl_certificate">Saved SSL certificate</string>
+    <string name="current_website_ssl_certificate">Current website SSL certificate</string>
+    <string name="load_an_encrypted_website">Load an encrypted website before opening Domain Settings to populate the current website SSL certificate.</string>
 
     <!-- Guide. -->
     <string name="privacy_browser_guide">Privacy Browser Guide</string>
index b97a3be..b46ea05 100644 (file)
         <item name="progressTintColor">@color/blue_700</item>
         <item name="navigationIconTintColor">@color/blue_800</item>
         <item name="findOnPageIconTintColor">@color/blue_800</item>
+        <item name="sslHeader">@color/blue_700</item>
         <item name="sslTitle">@color/blue_900</item>
         <item name="urlHistoryText">@color/black</item>
         <item name="redText">@color/red_a700</item>
+        <item name="dialogTabLayoutTheme">@style/PrivacyBrowserTabLayoutDialogLight</item>
     </style>
 
     <!-- `windowActionModeOverlay` makes the contextual app bar cover the support app bar.  `colorPrimaryDark` goes behind the status bar, which is then darkened by the overlay.-->
         <item name="tabIndicatorColor">@color/white</item>
     </style>
 
+    <style name="PrivacyBrowserTabLayoutDialogLight" parent="Widget.Design.TabLayout" >
+        <item name="android:textColorPrimary">@color/blue_700</item>
+        <item name="android:textColorSecondary">@color/blue_700_50</item>
+    </style>
+
     <style name="PrivacyBrowserAlertDialogLight" parent="Theme.AppCompat.Light.Dialog.Alert" >
         <item name="colorAccent">@color/blue_700</item>
     </style>
         <item name="progressTintColor">@color/blue_600</item>
         <item name="navigationIconTintColor">@color/blue_600</item>
         <item name="findOnPageIconTintColor">@color/blue_600</item>
+        <item name="sslHeader">@color/blue_400</item>
         <item name="sslTitle">@color/blue_700</item>
         <item name="urlHistoryText">@color/gray_200</item>
         <item name="redText">@color/red_900</item>