Implement IP Address Pinning. https://redmine.stoutner.com/issues/212
authorSoren Stoutner <soren@stoutner.com>
Tue, 29 Jan 2019 22:19:47 +0000 (15:19 -0700)
committerSoren Stoutner <soren@stoutner.com>
Tue, 29 Jan 2019 22:19:47 +0000 (15:19 -0700)
25 files changed:
app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/ImportExportActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFileDialog.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java [deleted file]
app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java
app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.java
app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java
app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java
app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportDatabaseHelper.java
app/src/main/res/layout/domain_settings_fragment.xml
app/src/main/res/layout/pinned_mismatch_linearlayout.xml [new file with mode: 0644]
app/src/main/res/layout/pinned_mismatch_scrollview.xml [new file with mode: 0644]
app/src/main/res/layout/pinned_ssl_certificate_mismatch_linearlayout.xml [deleted file]
app/src/main/res/layout/pinned_ssl_certificate_mismatch_scrollview.xml [deleted file]
app/src/main/res/layout/ssl_certificate_error.xml
app/src/main/res/menu/webview_options_menu.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values-tr/strings.xml
app/src/main/res/values/strings.xml

index 1d9a62a..7dbb6bf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2017-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -224,7 +224,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                 deleteMenuItem.setVisible(true);
 
                 // Hide `add_domain_fab`.
-                addDomainFAB.setVisibility(View.GONE);
+                addDomainFAB.hide();
 
                 // Display `domainSettingsFragment`.
                 supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
@@ -255,7 +255,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                     deleteMenuItem.setVisible(true);
 
                     // Hide `add_domain_fab`.
-                    addDomainFAB.setVisibility(View.GONE);
+                    addDomainFAB.hide();
 
                     // Display `domainSettingsFragment`.
                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
@@ -317,8 +317,8 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                     // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
                     populateDomainsListView(-1);
 
-                    // Display the add domain FAB.
-                    addDomainFAB.setVisibility(View.VISIBLE);
+                    // Show the add domain FAB.
+                    addDomainFAB.show();
 
                     // Hide the delete menu item.
                     deleteMenuItem.setVisible(false);
@@ -361,8 +361,8 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                     supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commit();
                     supportFragmentManager.executePendingTransactions();
 
-                    // Display `addDomainFAB`.
-                    addDomainFAB.setVisibility(View.VISIBLE);
+                    // Show the add domain FAB.
+                    addDomainFAB.show();
 
                     // Hide `deleteMenuItem`.
                     deleteMenuItem.setVisible(false);
@@ -453,9 +453,8 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
                                             // Display `domainSettingsFragment`.
                                             supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commit();
 
-                                            // Hide `add_domain_fab`.
-                                            FloatingActionButton addDomainFAB = findViewById(R.id.add_domain_fab);
-                                            addDomainFAB.setVisibility(View.GONE);
+                                            // Hide the add domain FAB.
+                                            addDomainFAB.hide();
 
                                             // Show and enable `deleteMenuItem`.
                                             deleteMenuItem.setVisible(true);
@@ -575,8 +574,8 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
             // Populate the list of domains.  `-1` highlights the first domain if in two-paned mode.  It has no effect in single-paned mode.
             populateDomainsListView(-1);
 
-            // Display the add domain FAB.
-            addDomainFAB.setVisibility(View.VISIBLE);
+            // Show the add domain FAB.
+            addDomainFAB.show();
 
             // Hide the delete menu item.
             deleteMenuItem.setVisible(false);
@@ -613,8 +612,8 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         if (twoPanedMode) {  // The device in in two-paned mode.
             populateDomainsListView(currentDomainDatabaseId);
         } else {  // The device is in single-paned mode.
-            // Hide `add_domain_fab`.
-            addDomainFAB.setVisibility(View.GONE);
+            // Hide the add domain FAB.
+            addDomainFAB.hide();
 
             // Show and enable `deleteMenuItem`.
             DomainsActivity.deleteMenuItem.setVisible(true);
@@ -635,26 +634,28 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
     public void saveDomainSettings(View view, Resources resources) {
         // Get handles for the domain settings.
         EditText domainNameEditText = view.findViewById(R.id.domain_settings_name_edittext);
-        Switch javaScriptSwitch = view.findViewById(R.id.domain_settings_javascript_switch);
-        Switch firstPartyCookiesSwitch = view.findViewById(R.id.domain_settings_first_party_cookies_switch);
-        Switch thirdPartyCookiesSwitch = view.findViewById(R.id.domain_settings_third_party_cookies_switch);
-        Switch domStorageSwitch = view.findViewById(R.id.domain_settings_dom_storage_switch);
-        Switch formDataSwitch = view.findViewById(R.id.domain_settings_form_data_switch);  // Form data can be removed once the minimum API >= 26.
-        Switch easyListSwitch = view.findViewById(R.id.domain_settings_easylist_switch);
-        Switch easyPrivacySwitch = view.findViewById(R.id.domain_settings_easyprivacy_switch);
-        Switch fanboysAnnoyanceSwitch = view.findViewById(R.id.domain_settings_fanboys_annoyance_list_switch);
-        Switch fanboysSocialBlockingSwitch = view.findViewById(R.id.domain_settings_fanboys_social_blocking_list_switch);
-        Switch ultraPrivacySwitch = view.findViewById(R.id.domain_settings_ultraprivacy_switch);
-        Switch blockAllThirdPartyRequestsSwitch = view.findViewById(R.id.domain_settings_block_all_third_party_requests_switch);
-        Spinner userAgentSpinner = view.findViewById(R.id.domain_settings_user_agent_spinner);
-        EditText customUserAgentEditText = view.findViewById(R.id.domain_settings_custom_user_agent_edittext);
-        Spinner fontSizeSpinner = view.findViewById(R.id.domain_settings_font_size_spinner);
-        Spinner swipeToRefreshSpinner = view.findViewById(R.id.domain_settings_swipe_to_refresh_spinner);
-        Spinner displayWebpageImagesSpinner = view.findViewById(R.id.domain_settings_display_webpage_images_spinner);
-        Spinner nightModeSpinner = view.findViewById(R.id.domain_settings_night_mode_spinner);
-        Switch pinnedSslCertificateSwitch = view.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
-        RadioButton savedSslCertificateRadioButton = view.findViewById(R.id.saved_ssl_certificate_radiobutton);
+        Switch javaScriptSwitch = view.findViewById(R.id.javascript_switch);
+        Switch firstPartyCookiesSwitch = view.findViewById(R.id.first_party_cookies_switch);
+        Switch thirdPartyCookiesSwitch = view.findViewById(R.id.third_party_cookies_switch);
+        Switch domStorageSwitch = view.findViewById(R.id.dom_storage_switch);
+        Switch formDataSwitch = view.findViewById(R.id.form_data_switch);  // Form data can be removed once the minimum API >= 26.
+        Switch easyListSwitch = view.findViewById(R.id.easylist_switch);
+        Switch easyPrivacySwitch = view.findViewById(R.id.easyprivacy_switch);
+        Switch fanboysAnnoyanceSwitch = view.findViewById(R.id.fanboys_annoyance_list_switch);
+        Switch fanboysSocialBlockingSwitch = view.findViewById(R.id.fanboys_social_blocking_list_switch);
+        Switch ultraPrivacySwitch = view.findViewById(R.id.ultraprivacy_switch);
+        Switch blockAllThirdPartyRequestsSwitch = view.findViewById(R.id.block_all_third_party_requests_switch);
+        Spinner userAgentSpinner = view.findViewById(R.id.user_agent_spinner);
+        EditText customUserAgentEditText = view.findViewById(R.id.custom_user_agent_edittext);
+        Spinner fontSizeSpinner = view.findViewById(R.id.font_size_spinner);
+        Spinner swipeToRefreshSpinner = view.findViewById(R.id.swipe_to_refresh_spinner);
+        Spinner displayWebpageImagesSpinner = view.findViewById(R.id.display_webpage_images_spinner);
+        Spinner nightModeSpinner = view.findViewById(R.id.night_mode_spinner);
+        Switch pinnedSslCertificateSwitch = view.findViewById(R.id.pinned_ssl_certificate_switch);
         RadioButton currentWebsiteCertificateRadioButton = view.findViewById(R.id.current_website_certificate_radiobutton);
+        Switch pinnedIpAddressesSwitch = view.findViewById(R.id.pinned_ip_addresses_switch);
+        RadioButton currentIpAddressesRadioButton = view.findViewById(R.id.current_ip_addresses_radiobutton);
+        TextView currentIpAddressesTextView = view.findViewById(R.id.current_ip_addresses_textview);
 
         // Extract the data for the domain settings.
         String domainNameString = domainNameEditText.getText().toString();
@@ -675,6 +676,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
         int nightModeInt = nightModeSpinner.getSelectedItemPosition();
         boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
+        boolean pinnedIpAddress = pinnedIpAddressesSwitch.isChecked();
 
         // Initialize the user agent name string.
         String userAgentName;
@@ -703,12 +705,12 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
         int fontSizeInt = Integer.parseInt(resources.getStringArray(R.array.domain_settings_font_size_entry_values)[fontSizePosition]);
 
         // Save the domain settings.
-        if (savedSslCertificateRadioButton.isChecked()) {  // The current certificate is being used.
-            // Update the database except for the certificate.
-            domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled,
+        domainsDatabaseHelper.updateDomain(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled,
                     domStorageEnabled, formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, ultraPrivacyEnabled, blockAllThirdPartyRequests,
-                    userAgentName, fontSizeInt, swipeToRefreshInt, nightModeInt, displayWebpageImagesInt, pinnedSslCertificate);
-        } else if (currentWebsiteCertificateRadioButton.isChecked()) {  // The certificate is being updated with the current website certificate.
+                    userAgentName, fontSizeInt, swipeToRefreshInt, nightModeInt, displayWebpageImagesInt, pinnedSslCertificate, pinnedIpAddress);
+
+        // Update the pinned SSL certificate if a new one is checked.
+        if (currentWebsiteCertificateRadioButton.isChecked()) {
             // Get the current website SSL certificate.
             SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
 
@@ -723,16 +725,17 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
             long endDateLong = currentWebsiteSslCertificate.getValidNotAfterDate().getTime();
 
             // Update the database.
-            domainsDatabaseHelper.updateDomainWithCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled, domStorageEnabled,
-                    formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, ultraPrivacyEnabled, blockAllThirdPartyRequests, userAgentName, fontSizeInt,
-                    swipeToRefreshInt, nightModeInt, 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, javaScriptEnabled, firstPartyCookiesEnabled, thirdPartyCookiesEnabled, domStorageEnabled,
-                    formDataEnabled, easyListEnabled, easyPrivacyEnabled, fanboysAnnoyanceEnabled, fanboysSocialBlockingEnabled, ultraPrivacyEnabled, blockAllThirdPartyRequests, userAgentName, fontSizeInt,
-                    swipeToRefreshInt, nightModeInt, displayWebpageImagesInt,false);
+            domainsDatabaseHelper.updatePinnedSslCertificate(currentDomainDatabaseId, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization,
+                    issuedByOrganizationalUnit, startDateLong, endDateLong);
+        }
+
+        // Update the pinned IP addresses if new ones are checked.
+        if (currentIpAddressesRadioButton.isChecked()) {
+            // Get the current IP addresses.
+            String currentIpAddresses = currentIpAddressesTextView.getText().toString();
+
+            // Update the database.
+            domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses);
         }
     }
 
index fb30046..a2d1797 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2018-2019 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -59,6 +59,7 @@ import com.stoutner.privacybrowser.helpers.ImportExportDatabaseHelper;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.SecureRandom;
 import java.util.Arrays;
@@ -674,7 +675,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp
                     secureRandom.nextBytes(saltByteArray);
 
                     // Convert the encryption password to a byte array.
-                    byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes("UTF-8");
+                    byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes(StandardCharsets.UTF_8);
 
                     // Append the salt to the encryption password byte array.  This protects against rainbow table attacks.
                     byte[] encryptionPasswordWithSaltByteArray = new byte[encryptionPasswordByteArray.length + saltByteArray.length];
@@ -832,7 +833,7 @@ public class ImportExportActivity extends AppCompatActivity implements ImportExp
                     encryptedImportFileInputStream.read(initializationVector);
 
                     // Convert the encryption password to a byte array.
-                    byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes("UTF-8");
+                    byte[] encryptionPasswordByteArray = encryptionPasswordString.getBytes(StandardCharsets.UTF_8);
 
                     // Append the salt to the encryption password byte array.  This protects against rainbow table attacks.
                     byte[] encryptionPasswordWithSaltByteArray = new byte[encryptionPasswordByteArray.length + saltByteArray.length];
index 9609894..1ced078 100644 (file)
@@ -47,6 +47,7 @@ import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
 import android.net.http.SslError;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
@@ -123,7 +124,7 @@ import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
-import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
+import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
 import com.stoutner.privacybrowser.helpers.AdHelper;
@@ -139,10 +140,13 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.lang.ref.WeakReference;
+import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
@@ -155,7 +159,7 @@ import java.util.Set;
 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
         CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
         DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
-        HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener,
+        HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener,
         SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
 
     // `darkTheme` is public static so it can be accessed from everywhere.
@@ -169,14 +173,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
     public static Bitmap favoriteIconBitmap;
 
-    // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
+    // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`.  It is also used in `onCreate()` and `applyDomainSettings`.
+    public static Bitmap favoriteIconDefaultBitmap;
+
+    // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`, and `PinnedMismatchDialog`.
     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
     public static String formattedUrlString;
 
-    // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`,
-    // and `ViewSslCertificateDialog`.  It is also used in `onCreate()`.
+    // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedMismatchDialog`, and `ViewSslCertificateDialog`.
+    // It is also used in `onCreate()`.
     public static SslCertificate sslCertificate;
 
+    // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`.
+    // It is also used in `onCreate()` and `GetHostIpAddresses()`.
+    public static String currentHostIpAddresses;
+
     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
     public static String orbotStatus;
 
@@ -257,18 +268,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
     public static String currentBookmarksFolder;
 
-    // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`.  It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
+    // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`.  It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
     public static int domainSettingsDatabaseId;
 
-    // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`.  They are also used in `onCreate()` and `applyDomainSettings()`.
-    public static String pinnedDomainSslIssuedToCNameString;
-    public static String pinnedDomainSslIssuedToONameString;
-    public static String pinnedDomainSslIssuedToUNameString;
-    public static String pinnedDomainSslIssuedByCNameString;
-    public static String pinnedDomainSslIssuedByONameString;
-    public static String pinnedDomainSslIssuedByUNameString;
-    public static Date pinnedDomainSslStartDate;
-    public static Date pinnedDomainSslEndDate;
+    // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`.  They are also used in `onCreate()` and `applyDomainSettings()`.
+    public static String pinnedSslIssuedToCName;
+    public static String pinnedSslIssuedToOName;
+    public static String pinnedSslIssuedToUName;
+    public static String pinnedSslIssuedByCName;
+    public static String pinnedSslIssuedByOName;
+    public static String pinnedSslIssuedByUName;
+    public static Date pinnedSslStartDate;
+    public static Date pinnedSslEndDate;
+    public static String pinnedHostIpAddresses;
 
     // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
     public final static int UNRECOGNIZED_USER_AGENT = -1;
@@ -285,9 +297,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
     private boolean navigatingHistory;
 
-    // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
-    private Bitmap favoriteIconDefaultBitmap;
-
     // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
     private DrawerLayout drawerLayout;
 
@@ -404,8 +413,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
     private String currentDomainName;
 
-    // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
-    private boolean ignorePinnedSslCertificate;
+    // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
+    private boolean pinnedSslCertificate;
+
+    // `pinnedIpAddress` is used in `onCreate()` and `applyDomainSettings()`.
+    private boolean pinnedIpAddresses;
+
+    // `ignorePinnedDomainInformation` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
+    private boolean ignorePinnedDomainInformation;
 
     // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
     private BroadcastReceiver orbotStatusBroadcastReceiver;
@@ -468,9 +483,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`.
     private boolean urlIsLoading;
 
-    // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
-    private boolean pinnedDomainSslCertificate;
-
     // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
     // and `loadBookmarksFolder()`.
     private BookmarksDatabaseHelper bookmarksDatabaseHelper;
@@ -1595,6 +1607,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Update the URL in urlTextBox when the page starts to load.
             @Override
             public void onPageStarted(WebView view, String url, Bitmap favicon) {
+                // Reset the list of host IP addresses.
+                currentHostIpAddresses = "";
+
                 // Reset the list of resource requests.
                 resourceRequests.clear();
 
@@ -1626,6 +1641,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     // Apply text highlighting to `urlTextBox`.
                     highlightUrlText();
 
+                    // Get a URI for the current URL.
+                    Uri currentUri = Uri.parse(formattedUrlString);
+
+                    // Get the IP addresses for the host.
+                    new GetHostIpAddresses(activity).execute(currentUri.getHost());
+
                     // Apply any custom domain settings if the URL was loaded by navigating history.
                     if (navigatingHistory) {
                         // Apply the domain settings.
@@ -1713,7 +1734,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     }
                 }
 
-                // Update `urlTextBox` and apply domain settings if not waiting on Orbot.
+                // Update the URL text box and apply domain settings if not waiting on Orbot.
                 if (!waitingForOrbot) {
                     // Check to see if `WebView` has set `url` to be `about:blank`.
                     if (url.equals("about:blank")) {  // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
@@ -1744,12 +1765,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                         }
                     }
 
-                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
+                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
                     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.
-                    // Also ignore if changes in the user agent causes an error while navigating history.
-                    if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) {
+                    // Check the current website information against any pinned domain information.
+                    if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
                         // Initialize the current SSL certificate variables.
                         String currentWebsiteIssuedToCName = "";
                         String currentWebsiteIssuedToOName = "";
@@ -1773,11 +1793,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             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`.
+                        // 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 = "";
+                        String pinnedSslStartDateString = "";
+                        String pinnedSslEndDateString = "";
 
                         // Convert the `Dates` to `Strings` if they are not `null`.
                         if (currentWebsiteSslStartDate != null) {
@@ -1788,23 +1808,26 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                             currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
                         }
 
-                        if (pinnedDomainSslStartDate != null) {
-                            pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
+                        if (pinnedSslStartDate != null) {
+                            pinnedSslStartDateString = pinnedSslStartDate.toString();
                         }
 
-                        if (pinnedDomainSslEndDate != null) {
-                            pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
+                        if (pinnedSslEndDate != null) {
+                            pinnedSslEndDateString = pinnedSslEndDate.toString();
                         }
 
-                        // Check to see if the pinned SSL certificate matches the current website certificate.
-                        if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) ||
-                                !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) ||
-                                !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
-                                !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {
-                            // The pinned SSL certificate doesn't match the current domain certificate.
-                            //Display the pinned SSL certificate mismatch `AlertDialog`.
-                            AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
-                            pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
+                        // Check to see if the pinned information matches the current information.
+                        if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) ||
+                                !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) ||
+                                !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) ||
+                                !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
+                                !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
+
+                            // Get a handle for the pinned mismatch alert dialog.
+                            AppCompatDialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
+
+                            // Show the pinned mismatch alert dialog.
+                            pinnedMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.pinned_mismatch));
                         }
                     }
                 }
@@ -1827,13 +1850,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 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.
+                if (pinnedSslCertificate &&
+                        currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
+                        currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
+                        currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
+                        currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
+
+                    // 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()`.
@@ -2849,20 +2872,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 mainWebView.reload();
                 return true;
 
-            case R.id.print:
-                // Get a `PrintManager` instance.
-                PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
-
-                // Convert `mainWebView` to `printDocumentAdapter`.
-                PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
-
-                // Remove the lint error below that `printManager` might be `null`.
-                assert printManager != null;
-
-                // Print the document.  The print attributes are `null`.
-                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
-                return true;
-
             case R.id.find_on_page:
                 // Hide the URL app bar.
                 supportAppBar.setVisibility(View.GONE);
@@ -2881,14 +2890,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 }, 200);
                 return true;
 
-            case R.id.add_to_homescreen:
-                // Show the alert dialog.
-                AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
-                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
-
-                //Everything else will be handled by the alert dialog and the associated listener below.
-                return true;
-
             case R.id.view_source:
                 // Launch the View Source activity.
                 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
@@ -2908,6 +2909,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
                 return true;
 
+            case R.id.print:
+                // Get a `PrintManager` instance.
+                PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+
+                // Convert `mainWebView` to `printDocumentAdapter`.
+                PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
+
+                // Remove the lint error below that `printManager` might be `null`.
+                assert printManager != null;
+
+                // Print the document.  The print attributes are `null`.
+                printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+                return true;
+
             case R.id.open_with_app:
                 // Create the open with intent with `ACTION_VIEW`.
                 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
@@ -2936,6 +2951,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 startActivity(Intent.createChooser(openWithBrowserIntent, getString(R.string.open_with)));
                 return true;
 
+            case R.id.add_to_homescreen:
+                // Show the alert dialog.
+                AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
+                createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+
+                //Everything else will be handled by the alert dialog and the associated listener below.
+                return true;
+
             case R.id.proxy_through_orbot:
                 // Toggle the proxy through Orbot variable.
                 proxyThroughOrbot = !proxyThroughOrbot;
@@ -3856,7 +3879,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
-    public void onSslMismatchBack() {
+    public void onPinnedMismatchBack() {
         if (mainWebView.canGoBack()) {  // There is a back page in the history.
             // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
             formattedUrlString = "";
@@ -3873,9 +3896,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
     }
 
     @Override
-    public void onSslMismatchProceed() {
-        // Do not check the pinned SSL certificate for this domain again until the domain changes.
-        ignorePinnedSslCertificate = true;
+    public void onPinnedMismatchProceed() {
+        // Do not check the pinned information for this domain again until the domain changes.
+        ignorePinnedDomainInformation = true;
     }
 
     @Override
@@ -4170,8 +4193,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             // Set the new `hostname` as the `currentDomainName`.
             currentDomainName = hostName;
 
-            // Reset `ignorePinnedSslCertificate`.
-            ignorePinnedSslCertificate = false;
+            // Reset the ignoring of pinned domain information.
+            ignorePinnedDomainInformation = false;
 
             // Reset the favorite icon if specified.
             if (resetFavoriteIcon) {
@@ -4263,13 +4286,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                 int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
                 int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
                 int 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));
+                pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
+                pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
+                pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
+                pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
+                pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
+                pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
+                pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+                pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
+                pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
 
                 // Set `nightMode` according to `nightModeInt`.  If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
                 switch (nightModeInt) {
@@ -4292,16 +4317,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
 
                 // 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;
+                    pinnedSslStartDate = null;
                 } else {
-                    pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+                    pinnedSslStartDate = 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;
+                    pinnedSslEndDate = null;
                 } else {
-                    pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                    pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
                 }
 
                 // Close `currentHostDomainSettingsCursor`.
@@ -4454,17 +4479,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
                     mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
                 }
 
-                // Reset the pinned SSL certificate information.
+                // Reset the pinned variables.
                 domainSettingsDatabaseId = -1;
-                pinnedDomainSslCertificate = false;
-                pinnedDomainSslIssuedToCNameString = "";
-                pinnedDomainSslIssuedToONameString = "";
-                pinnedDomainSslIssuedToUNameString = "";
-                pinnedDomainSslIssuedByCNameString = "";
-                pinnedDomainSslIssuedByONameString = "";
-                pinnedDomainSslIssuedByUNameString = "";
-                pinnedDomainSslStartDate = null;
-                pinnedDomainSslEndDate = null;
+                pinnedSslCertificate = false;
+                pinnedSslIssuedToCName = "";
+                pinnedSslIssuedToOName = "";
+                pinnedSslIssuedToUName = "";
+                pinnedSslIssuedByCName = "";
+                pinnedSslIssuedByOName = "";
+                pinnedSslIssuedByUName = "";
+                pinnedSslStartDate = null;
+                pinnedSslEndDate = null;
+                pinnedIpAddresses = false;
+                pinnedHostIpAddresses = "";
 
                 // Set third-party cookies status if API >= 21.
                 if (Build.VERSION.SDK_INT >= 21) {
@@ -4799,4 +4826,70 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook
             bookmarksTitleTextView.setText(currentBookmarksFolder);
         }
     }
+
+    // This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `String` contains the results.
+    private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
+        // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
+        private final WeakReference<Activity> activityWeakReference;
+
+        GetHostIpAddresses(Activity activity) {
+            // Populate the weak references.
+            activityWeakReference = new WeakReference<>(activity);
+        }
+
+        @Override
+        protected String doInBackground(String... domainName) {
+            // Get handles for the activity and the alert dialog.
+            Activity activity = activityWeakReference.get();
+
+            // Abort if the activity or the dialog is gone.
+            if ((activity == null) || activity.isFinishing()) {
+                // Return an empty spannable string builder.
+                return "";
+            }
+
+            // Initialize an IP address string builder.
+            StringBuilder ipAddresses = new StringBuilder();
+
+            // Get an array with the IP addresses for the host.
+            try {
+                // Get an array with all the IP addresses for the domain.
+                InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
+
+                // Add each IP address to the string builder.
+                for (InetAddress inetAddress : inetAddressesArray) {
+                    if (ipAddresses.length() == 0) {  // This is the first IP address.
+                        // Add the IP address to the string builder.
+                        ipAddresses.append(inetAddress.getHostAddress());
+                    } else {  // This is not the first IP address.
+                        // Add a line break to the string builder first.
+                        ipAddresses.append("\n");
+
+                        // Add the IP address to the string builder.
+                        ipAddresses.append(inetAddress.getHostAddress());
+                    }
+                }
+            } catch (UnknownHostException exception) {
+                // Do nothing.
+            }
+
+            // Return the string.
+            return ipAddresses.toString();
+        }
+
+        // `onPostExecute()` operates on the UI thread.
+        @Override
+        protected void onPostExecute(String ipAddresses) {
+            // Get handles for the activity and the alert dialog.
+            Activity activity = activityWeakReference.get();
+
+            // Abort if the activity or the alert dialog is gone.
+            if ((activity == null) || activity.isFinishing()) {
+                return;
+            }
+
+            // Store the IP addresses.
+            currentHostIpAddresses = ipAddresses;
+        }
+    }
 }
\ No newline at end of file
index 50e7d65..8e7f5e9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2017-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -308,7 +308,7 @@ public class ViewSourceActivity extends AppCompatActivity {
             Activity viewSourceActivity = activityWeakReference.get();
 
             // Abort if the activity is gone.
-            if ((viewSourceActivity == null) || (viewSourceActivity.isFinishing())) {
+            if ((viewSourceActivity == null) || viewSourceActivity.isFinishing()) {
                 return;
             }
 
@@ -334,7 +334,7 @@ public class ViewSourceActivity extends AppCompatActivity {
             Activity activity = activityWeakReference.get();
 
             // Abort if the activity is gone.
-            if ((activity == null) || (activity.isFinishing())) {
+            if ((activity == null) || activity.isFinishing()) {
                 return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
             }
 
@@ -667,7 +667,7 @@ public class ViewSourceActivity extends AppCompatActivity {
             Activity activity = activityWeakReference.get();
 
             // Abort if the activity is gone.
-            if ((activity == null) || (activity.isFinishing())) {
+            if ((activity == null) || activity.isFinishing()) {
                 return;
             }
 
index cf94812..2ade1ea 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -66,13 +66,14 @@ public class DownloadFileDialog extends AppCompatDialogFragment {
         // Create a variable for the file name string.
         String fileNameString;
 
-        // Parse `filename` from `contentDisposition`.
+        // Parse the filename from `contentDisposition`.
         if (contentDisposition.contains("filename=\"")) {  // The file name is contained in a string surrounded by `""`.
             fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=\"") + 10, contentDisposition.indexOf("\"", contentDisposition.indexOf("filename=\"") + 10));
-        } else if (contentDisposition.contains("filename=") && ((contentDisposition.indexOf(";", contentDisposition.indexOf("filename=") + 9)) > 0 )) {  // The file name is contained in a string beginning with `filename=` and ending with `;`.
+        } else if (contentDisposition.contains("filename=") && ((contentDisposition.indexOf(";", contentDisposition.indexOf("filename=") + 9)) > 0 )) {
+            // The file name is contained in a string beginning with `filename=` and ending with `;`.
             fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9, contentDisposition.indexOf(";", contentDisposition.indexOf("filename=") + 9));
         } else if (contentDisposition.contains("filename=")) {  // The file name is contained in a string beginning with `filename=` and proceeding to the end of `contentDisposition`.
-            fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9, contentDisposition.length());
+            fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9);
         } else {  // `contentDisposition` does not contain the filename, so use the last path segment of the URL.
             Uri downloadUri = Uri.parse(urlString);
             fileNameString = downloadUri.getLastPathSegment();
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.java
new file mode 100644 (file)
index 0000000..2d506dc
--- /dev/null
@@ -0,0 +1,472 @@
+/*
+ * Copyright © 2017-2019 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.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+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;
+// `AppCompatDialogFragment` is used instead of `DialogFragment` to avoid an error 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.view.WindowManager;
+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;
+
+public class PinnedMismatchDialog extends AppCompatDialogFragment {
+    // Instantiate the class variables.
+    private PinnedMismatchListener pinnedMismatchListener;
+    private LayoutInflater layoutInflater;
+    private String currentSslIssuedToCName;
+    private String currentSslIssuedToOName;
+    private String currentSslIssuedToUName;
+    private String currentSslIssuedByCName;
+    private String currentSslIssuedByOName;
+    private String currentSslIssuedByUName;
+    private Date currentSslStartDate;
+    private Date currentSslEndDate;
+    private boolean pinnedSslCertificate;
+    private boolean pinnedIpAddresses;
+
+    // The public interface is used to send information back to the parent activity.
+    public interface PinnedMismatchListener {
+        void onPinnedMismatchBack();
+
+        void onPinnedMismatchProceed();
+    }
+
+    // Check to make sure that the parent activity implements the listener.
+    public void onAttach(Context context) {
+        // Run the default commands.
+        super.onAttach(context);
+
+        // Get a handle for `PinnedSslCertificateMismatchListener` from the launching context.
+        pinnedMismatchListener = (PinnedMismatchListener) context;
+    }
+
+    public static PinnedMismatchDialog displayDialog(boolean pinnedSslCertificate, boolean pinnedIpAddresses) {
+        // Create an arguments bundle.
+        Bundle argumentsBundle = new Bundle();
+
+        // Store the variables in the bundle.
+        argumentsBundle.putBoolean("Pinned_SSL_Certificate", pinnedSslCertificate);
+        argumentsBundle.putBoolean("Pinned_IP_Addresses", pinnedIpAddresses);
+
+        // Add the arguments bundle to this instance of `PinnedMismatchDialog`.
+        PinnedMismatchDialog thisPinnedMismatchDialog = new PinnedMismatchDialog();
+        thisPinnedMismatchDialog.setArguments(argumentsBundle);
+        return thisPinnedMismatchDialog;
+    }
+
+    // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
+    @SuppressLint("InflateParams")
+    @Override
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Remove the incorrect lint warning that `getActivity()` might be null.
+        assert getActivity() != null;
+
+        // Get the activity's layout inflater.
+        layoutInflater = getActivity().getLayoutInflater();
+
+        // Use an alert dialog builder to create the alert dialog.
+        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);
+        } else {
+            // Set the dialog theme.
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
+        }
+
+        // Remove the incorrect lint warning below that `.getArguments.getBoolean()` might be null.
+        assert getArguments() != null;
+
+        // Get the variables from the bundle.
+        pinnedSslCertificate = getArguments().getBoolean("Pinned_SSL_Certificate");
+        pinnedIpAddresses = getArguments().getBoolean("Pinned_IP_Addresses");
+
+        if (MainWebViewActivity.favoriteIconBitmap.equals(MainWebViewActivity.favoriteIconDefaultBitmap)) {
+            // Set the icon according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark);
+            } else {
+                dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light);
+            }
+        } else {
+            // Create a drawable version of the favorite icon.
+            Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIconBitmap);
+
+            // Set the icon.
+            dialogBuilder.setIcon(favoriteIconDrawable);
+        }
+
+        // Setup the neutral button.
+        dialogBuilder.setNeutralButton(R.string.update, (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 `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 SSL certificate if it is pinned.
+            if (pinnedSslCertificate) {
+                // Update the pinned SSL certificate in the domain database.
+                domainsDatabaseHelper.updatePinnedSslCertificate(MainWebViewActivity.domainSettingsDatabaseId, currentSslIssuedToCName, currentSslIssuedToOName, currentSslIssuedToUName,
+                        currentSslIssuedByCName, currentSslIssuedByOName, currentSslIssuedByUName, currentSslStartDateLong, currentSslEndDateLong);
+
+                // Update the pinned SSL certificate class variables to match the information that is now in the database.
+                MainWebViewActivity.pinnedSslIssuedToCName = currentSslIssuedToCName;
+                MainWebViewActivity.pinnedSslIssuedToOName = currentSslIssuedToOName;
+                MainWebViewActivity.pinnedSslIssuedToUName = currentSslIssuedToUName;
+                MainWebViewActivity.pinnedSslIssuedByCName = currentSslIssuedByCName;
+                MainWebViewActivity.pinnedSslIssuedByOName = currentSslIssuedByOName;
+                MainWebViewActivity.pinnedSslIssuedByUName = currentSslIssuedByUName;
+                MainWebViewActivity.pinnedSslStartDate = currentSslStartDate;
+                MainWebViewActivity.pinnedSslEndDate = currentSslEndDate;
+            }
+
+            // Update the IP addresses if they are pinned.
+            if (pinnedIpAddresses) {
+                // Update the pinned IP addresses in the domain database.
+                domainsDatabaseHelper.updatePinnedIpAddresses(MainWebViewActivity.domainSettingsDatabaseId, MainWebViewActivity.currentHostIpAddresses);
+
+                // Update the pinned IP addresses class variable to match the information that is now in the database.
+                MainWebViewActivity.pinnedHostIpAddresses = MainWebViewActivity.currentHostIpAddresses;
+            }
+        });
+
+        // Setup the negative button.
+        dialogBuilder.setNegativeButton(R.string.back, (DialogInterface dialog, int which) -> {
+            // Call the `onSslMismatchBack` public interface to send the `WebView` back one page.
+            pinnedMismatchListener.onPinnedMismatchBack();
+        });
+
+        // Setup the positive button.
+        dialogBuilder.setPositiveButton(R.string.proceed, (DialogInterface dialog, int which) -> {
+            // Call the `onSslMismatchProceed` public interface.
+            pinnedMismatchListener.onPinnedMismatchProceed();
+        });
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.pinned_mismatch);
+
+        // Set the layout.  The parent view is `null` because it will be assigned by `AlertDialog`.
+        dialogBuilder.setView(layoutInflater.inflate(R.layout.pinned_mismatch_linearlayout, null));
+
+        // Create an alert dialog from the alert dialog builder.
+        final AlertDialog alertDialog = dialogBuilder.create();
+
+        // Disable screenshots if not allowed.
+        if (!MainWebViewActivity.allowScreenshots) {
+            // Remove the warning below that `getWindow()` might be null.
+            assert alertDialog.getWindow() != null;
+
+            // Disable screenshots.
+            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        }
+
+        // Show the alert dialog so the items in the layout can be modified.
+        alertDialog.show();
+
+        //  Setup the view pager.
+        WrapVerticalContentViewPager wrapVerticalContentViewPager = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager);
+        wrapVerticalContentViewPager.setAdapter(new pagerAdapter());
+
+        // Setup the tab layout and connect it to the view pager.
+        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(@NonNull View view, @NonNull 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);
+            } else {  // The pinned SSL certificate tab.
+                return getString(R.string.pinned);
+            }
+        }
+
+        @Override
+        @NonNull
+        public Object instantiateItem(@NonNull ViewGroup container, int position) {
+            // Inflate the scroll view for this tab.
+            ViewGroup tabViewGroup = (ViewGroup) layoutInflater.inflate(R.layout.pinned_mismatch_scrollview, container, false);
+
+            // Get handles for the `TextViews`.
+            TextView domainNameTextView = tabViewGroup.findViewById(R.id.domain_name);
+            TextView ipAddressesTextView = tabViewGroup.findViewById(R.id.ip_addresses);
+            TextView issuedToCNameTextView = tabViewGroup.findViewById(R.id.issued_to_cname);
+            TextView issuedToONameTextView = tabViewGroup.findViewById(R.id.issued_to_oname);
+            TextView issuedToUNameTextView = tabViewGroup.findViewById(R.id.issued_to_uname);
+            TextView issuedByCNameTextView = tabViewGroup.findViewById(R.id.issued_by_cname);
+            TextView issuedByONameTextView = tabViewGroup.findViewById(R.id.issued_by_oname);
+            TextView issuedByUNameTextView = tabViewGroup.findViewById(R.id.issued_by_uname);
+            TextView startDateTextView = tabViewGroup.findViewById(R.id.start_date);
+            TextView endDateTextView = tabViewGroup.findViewById(R.id.end_date);
+
+            // Setup the labels.
+            String domainNameLabel = getString(R.string.domain_label) + "  ";
+            String ipAddressesLabel = getString(R.string.ip_addresses) + "  ";
+            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 a URI for the URL.
+            Uri currentUri = Uri.parse(MainWebViewActivity.formattedUrlString);
+
+            // Get the current host from the URI.
+            String domainName = currentUri.getHost();
+
+            // 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) {
+                currentSslIssuedToCName = sslCertificate.getIssuedTo().getCName();
+                currentSslIssuedToOName = sslCertificate.getIssuedTo().getOName();
+                currentSslIssuedToUName = sslCertificate.getIssuedTo().getUName();
+                currentSslIssuedByCName = sslCertificate.getIssuedBy().getCName();
+                currentSslIssuedByOName = sslCertificate.getIssuedBy().getOName();
+                currentSslIssuedByUName = sslCertificate.getIssuedBy().getUName();
+                currentSslStartDate = sslCertificate.getValidNotBeforeDate();
+                currentSslEndDate = sslCertificate.getValidNotAfterDate();
+            } else {
+                // Initialize the current website SSL certificate variables with blank information.
+                currentSslIssuedToCName = "";
+                currentSslIssuedToOName = "";
+                currentSslIssuedToUName = "";
+                currentSslIssuedByCName = "";
+                currentSslIssuedByOName = "";
+                currentSslIssuedByUName = "";
+            }
+
+            // Setup the domain name spannable string builder.
+            SpannableStringBuilder domainNameStringBuilder = new SpannableStringBuilder(domainNameLabel + domainName);
+
+            // Initialize the spannable string builders.
+            SpannableStringBuilder ipAddressesStringBuilder;
+            SpannableStringBuilder issuedToCNameStringBuilder;
+            SpannableStringBuilder issuedToONameStringBuilder;
+            SpannableStringBuilder issuedToUNameStringBuilder;
+            SpannableStringBuilder issuedByCNameStringBuilder;
+            SpannableStringBuilder issuedByONameStringBuilder;
+            SpannableStringBuilder issuedByUNameStringBuilder;
+            SpannableStringBuilder startDateStringBuilder;
+            SpannableStringBuilder endDateStringBuilder;
+
+            // Setup the spannable string builders for each tab.
+            if (position == 0) {  // Setup the current settings tab.
+                // Create the string builders.
+                ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + MainWebViewActivity.currentHostIpAddresses);
+                issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedToCName);
+                issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedToOName);
+                issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedToUName);
+                issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedByCName);
+                issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedByOName);
+                issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedByUName);
+
+                // 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 settings tab.
+                // Create the string builders.
+                ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + MainWebViewActivity.pinnedHostIpAddresses);
+                issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedSslIssuedToCName);
+                issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedSslIssuedToOName);
+                issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedSslIssuedToUName);
+                issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedSslIssuedByCName);
+                issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedSslIssuedByOName);
+                issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedSslIssuedByUName);
+
+                // Set the dates if they aren't `null`.
+                if (MainWebViewActivity.pinnedSslStartDate == null) {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
+                } else {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG)
+                            .format(MainWebViewActivity.pinnedSslStartDate));
+                }
+
+                if (MainWebViewActivity.pinnedSslEndDate == null) {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
+                } else {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(MainWebViewActivity.pinnedSslEndDate));
+                }
+            }
+
+            // Create a red foreground color span.  The deprecated `getResources().getColor` must be used until the minimum API >= 23.
+            @SuppressWarnings("deprecation") ForegroundColorSpan redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+
+            // Create a blue foreground color span.
+            ForegroundColorSpan blueColorSpan;
+
+            // Set the blue color span according to the theme.  The deprecated `getResources().getColor` must be used until the minimum 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 to be blue.
+            domainNameStringBuilder.setSpan(blueColorSpan, domainNameLabel.length(), domainNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+            // Color coordinate the IP addresses if they are pinned.
+            if (pinnedIpAddresses) {
+                if (MainWebViewActivity.currentHostIpAddresses.equals(MainWebViewActivity.pinnedHostIpAddresses)) {
+                    ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                } else {
+                    ipAddressesStringBuilder.setSpan(redColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+            }
+
+            // Color coordinate the SSL certificate fields if they are pinned.
+            if (pinnedSslCertificate) {
+                if (currentSslIssuedToCName.equals(MainWebViewActivity.pinnedSslIssuedToCName)) {
+                    issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                } else {
+                    issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+
+                if (currentSslIssuedToOName.equals(MainWebViewActivity.pinnedSslIssuedToOName)) {
+                    issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                } else {
+                    issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+
+                if (currentSslIssuedToUName.equals(MainWebViewActivity.pinnedSslIssuedToUName)) {
+                    issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                } else {
+                    issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+
+                if (currentSslIssuedByCName.equals(MainWebViewActivity.pinnedSslIssuedByCName)) {
+                    issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                } else {
+                    issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+
+                if (currentSslIssuedByOName.equals(MainWebViewActivity.pinnedSslIssuedByOName)) {
+                    issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                } else {
+                    issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                }
+
+                if (currentSslIssuedByUName.equals(MainWebViewActivity.pinnedSslIssuedByUName)) {
+                    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) && currentSslStartDate.equals(MainWebViewActivity.pinnedSslStartDate)) {
+                    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) && currentSslEndDate.equals(MainWebViewActivity.pinnedSslEndDate)) {
+                    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.
+            domainNameTextView.setText(domainNameStringBuilder);
+            ipAddressesTextView.setText(ipAddressesStringBuilder);
+            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;
+        }
+    }
+}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java
deleted file mode 100644 (file)
index 7404708..0000000
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright © 2017-2018 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;
-// `AppCompatDialogFragment` is used instead of `DialogFragment` to avoid an error 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.view.WindowManager;
-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;
-
-public class PinnedSslCertificateMismatchDialog extends AppCompatDialogFragment {
-    // Instantiate the class variables.
-    private PinnedSslCertificateMismatchListener pinnedSslCertificateMismatchListener;
-    private LayoutInflater layoutInflater;
-    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();
-    }
-
-    // Check to make sure that the parent activity implements the listener.
-    public void onAttach(Context context) {
-        // Run the default commands.
-        super.onAttach(context);
-
-        // Get a handle for `PinnedSslCertificateMismatchListener` from the launching context.
-        pinnedSslCertificateMismatchListener = (PinnedSslCertificateMismatchListener) context;
-    }
-
-    // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
-    @SuppressLint("InflateParams")
-    @Override
-    @NonNull
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        // Remove the incorrect lint warning that `getActivity()` might be null.
-        assert getActivity() != null;
-
-        // Get the activity's layout inflater.
-        layoutInflater = getActivity().getLayoutInflater();
-
-        // Use an alert dialog builder to create the alert dialog.
-        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, (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 `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, (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, (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 alert dialog from the alert dialog builder.
-        final AlertDialog alertDialog = dialogBuilder.create();
-
-        // Disable screenshots if not allowed.
-        if (!MainWebViewActivity.allowScreenshots) {
-            // Remove the warning below that `getWindow()` might be null.
-            assert alertDialog.getWindow() != null;
-
-            // Disable screenshots.
-            alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
-        }
-
-        // Show the alert dialog so the items in the layout can be modified.
-        alertDialog.show();
-
-        //  Setup the view pager.
-        WrapVerticalContentViewPager wrapVerticalContentViewPager = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager);
-        wrapVerticalContentViewPager.setAdapter(new pagerAdapter());
-
-        // Setup the tab layout and connect it to the view pager.
-        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(@NonNull View view, @NonNull 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
-        @NonNull
-        public Object instantiateItem(@NonNull 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 = tabViewGroup.findViewById(R.id.issued_to_cname);
-            TextView issuedToONameTextView = tabViewGroup.findViewById(R.id.issued_to_oname);
-            TextView issuedToUNameTextView = tabViewGroup.findViewById(R.id.issued_to_uname);
-            TextView issuedByCNameTextView = tabViewGroup.findViewById(R.id.issued_by_cname);
-            TextView issuedByONameTextView = tabViewGroup.findViewById(R.id.issued_by_oname);
-            TextView issuedByUNameTextView = tabViewGroup.findViewById(R.id.issued_by_uname);
-            TextView startDateTextView = tabViewGroup.findViewById(R.id.start_date);
-            TextView endDateTextView = 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 0c93025..76732ff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
+import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.net.Uri;
 import android.net.http.SslCertificate;
 import android.net.http.SslError;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 // `AppCompatDialogFragment` is used instead of `DialogFragment` to avoid an error on API <=22.
@@ -40,6 +43,9 @@ import android.widget.TextView;
 import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.activities.MainWebViewActivity;
 
+import java.lang.ref.WeakReference;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.text.DateFormat;
 import java.util.Date;
 
@@ -106,7 +112,7 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment {
 
         // Get the components of the SSL error message from the bundle.
         int primaryErrorInt = getArguments().getInt("PrimaryErrorInt");
-        String urlWithError = getArguments().getString("UrlWithError");
+        String urlWithErrors = getArguments().getString("UrlWithError");
         String issuedToCName = getArguments().getString("IssuedToCName");
         String issuedToOName = getArguments().getString("IssuedToOName");
         String issuedToUName = getArguments().getString("IssuedToUName");
@@ -165,22 +171,28 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment {
             alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
         }
 
-        // We have to show the alert dialog before we can modify the content.
+        // Get a URI for the URL with errors.
+        Uri uriWithErrors = Uri.parse(urlWithErrors);
+
+        // Get the IP addresses for the URI.
+        new GetIpAddresses(getActivity(), alertDialog).execute(uriWithErrors.getHost());
+
+        // The alert dialog must be shown before the contents can be modified.
         alertDialog.show();
 
         // Get handles for the `TextViews`
         TextView primaryErrorTextView = alertDialog.findViewById(R.id.primary_error);
-        TextView urlTextView = alertDialog.findViewById(R.id.url_error_dialog);
-        TextView issuedToCNameTextView = alertDialog.findViewById(R.id.issued_to_cname_error_dialog);
-        TextView issuedToONameTextView = alertDialog.findViewById(R.id.issued_to_oname_error_dialog);
-        TextView issuedToUNameTextView = alertDialog.findViewById(R.id.issued_to_uname_error_dialog);
+        TextView urlTextView = alertDialog.findViewById(R.id.url);
+        TextView issuedToCNameTextView = alertDialog.findViewById(R.id.issued_to_cname);
+        TextView issuedToONameTextView = alertDialog.findViewById(R.id.issued_to_oname);
+        TextView issuedToUNameTextView = alertDialog.findViewById(R.id.issued_to_uname);
         TextView issuedByTextView = alertDialog.findViewById(R.id.issued_by_textview);
-        TextView issuedByCNameTextView = alertDialog.findViewById(R.id.issued_by_cname_error_dialog);
-        TextView issuedByONameTextView = alertDialog.findViewById(R.id.issued_by_oname_error_dialog);
-        TextView issuedByUNameTextView = alertDialog.findViewById(R.id.issued_by_uname_error_dialog);
+        TextView issuedByCNameTextView = alertDialog.findViewById(R.id.issued_by_cname);
+        TextView issuedByONameTextView = alertDialog.findViewById(R.id.issued_by_oname);
+        TextView issuedByUNameTextView = alertDialog.findViewById(R.id.issued_by_uname);
         TextView validDatesTextView = alertDialog.findViewById(R.id.valid_dates_textview);
-        TextView startDateTextView = alertDialog.findViewById(R.id.start_date_error_dialog);
-        TextView endDateTextView = alertDialog.findViewById(R.id.end_date_error_dialog);
+        TextView startDateTextView = alertDialog.findViewById(R.id.start_date);
+        TextView endDateTextView = alertDialog.findViewById(R.id.end_date);
 
         // Setup the common strings.
         String urlLabel = getString(R.string.url_label) + "  ";
@@ -190,8 +202,8 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment {
         String startDateLabel = getString(R.string.start_date) + "  ";
         String endDateLabel = getString(R.string.end_date) + "  ";
 
-        // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text.
-        SpannableStringBuilder urlStringBuilder = new SpannableStringBuilder(urlLabel + urlWithError);
+        // Create a spannable string builder for each text view that needs multiple colors of text.
+        SpannableStringBuilder urlStringBuilder = new SpannableStringBuilder(urlLabel + urlWithErrors);
         SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCName);
         SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToOName);
         SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUName);
@@ -201,13 +213,13 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment {
         SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + startDate);
         SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder((endDateLabel + endDate));
 
-        // Create a red `ForegroundColorSpan`.  We have to use the deprecated `getColor` until API >= 23.
+        // Create a red foreground color span.  The deprecated `getResources().getColor` must be used until the minimum 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.
+        // Set a blue color span according to the theme.  The deprecated `getResources().getColor` must be used until the minimum API >= 23.
         if (MainWebViewActivity.darkTheme) {
             //noinspection deprecation
             blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400));
@@ -242,7 +254,7 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment {
                 break;
 
             case SslError.SSL_UNTRUSTED:
-                // Change the `issuesByTextView` text to red.  We have to use the deprecated `getColor()` until API >= 23.
+                // Change the issued by text view text to red.  The deprecated `getResources().getColor` must be used until the minimum API >= 23.
                 issuedByTextView.setTextColor(getResources().getColor(R.color.red_a700));
 
                 // Change the issued by span color to red.
@@ -255,7 +267,7 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment {
                 break;
 
             case SslError.SSL_DATE_INVALID:
-                // Change the `validDatesTextView` text to red.  We have to use the deprecated `getColor()` until API >= 23.
+                // Change the valid dates text view text to red.  The deprecated `getResources().getColor` must be used until the minimum API >= 23.
                 validDatesTextView.setTextColor(getResources().getColor(R.color.red_a700));
 
                 // Change the date span colors to red.
@@ -301,7 +313,101 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment {
         startDateTextView.setText(startDateStringBuilder);
         endDateTextView.setText(endDateStringBuilder);
 
-        // `onCreateDialog` requires the return of an `AlertDialog`.
+        // `onCreateDialog` requires the return of an alert dialog.
         return alertDialog;
     }
+
+
+    // This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `SpannableStringBuilder` contains the results.
+    private static class GetIpAddresses extends AsyncTask<String, Void, SpannableStringBuilder> {
+        // The weak references are used to determine if the activity or the alert dialog have disappeared while the AsyncTask is running.
+        private WeakReference<Activity> activityWeakReference;
+        private WeakReference<AlertDialog> alertDialogWeakReference;
+
+        GetIpAddresses(Activity activity, AlertDialog alertDialog) {
+            // Populate the weak references.
+            activityWeakReference = new WeakReference<>(activity);
+            alertDialogWeakReference = new WeakReference<>(alertDialog);
+        }
+
+        @Override
+        protected SpannableStringBuilder doInBackground(String... domainName) {
+            // Get handles for the activity and the alert dialog.
+            Activity activity = activityWeakReference.get();
+            AlertDialog alertDialog = alertDialogWeakReference.get();
+
+            // Abort if the activity or the dialog is gone.
+            if ((activity == null) || (activity.isFinishing()) || (alertDialog == null)) {
+                return new SpannableStringBuilder();
+            }
+
+            // Initialize an IP address string builder.
+            StringBuilder ipAddresses = new StringBuilder();
+
+            // Get an array with the IP addresses for the host.
+            try {
+                // Get an array with all the IP addresses for the domain.
+                InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
+
+                // Add each IP address to the string builder.
+                for (InetAddress inetAddress : inetAddressesArray) {
+                    if (ipAddresses.length() == 0) {  // This is the first IP address.
+                        // Add the IP Address to the string builder.
+                        ipAddresses.append(inetAddress.getHostAddress());
+                    } else {  // This is not the first IP address.
+                        // Add a line break to the string builder first.
+                        ipAddresses.append("\n");
+
+                        // Add the IP address to the string builder.
+                        ipAddresses.append(inetAddress.getHostAddress());
+                    }
+                }
+            } catch (UnknownHostException exception) {
+                // Do nothing.
+            }
+
+            // Set the label.
+            String ipAddressesLabel = activity.getString(R.string.ip_addresses) + "  ";
+
+            // Create a spannable string builder.
+            SpannableStringBuilder ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + ipAddresses);
+
+            // Create a blue foreground color span.
+            ForegroundColorSpan blueColorSpan;
+
+            // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until the minimum API >= 23.
+            if (MainWebViewActivity.darkTheme) {
+                //noinspection deprecation
+                blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.blue_400));
+            } else {
+                //noinspection deprecation
+                blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.blue_700));
+            }
+
+            // Set the string builder to display the certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+            // Return the formatted string.
+            return ipAddressesStringBuilder;
+        }
+
+        // `onPostExecute()` operates on the UI thread.
+        @Override
+        protected void onPostExecute(SpannableStringBuilder ipAddresses) {
+            // Get handles for the activity and the alert dialog.
+            Activity activity = activityWeakReference.get();
+            AlertDialog alertDialog = alertDialogWeakReference.get();
+
+            // Abort if the activity or the alert dialog is gone.
+            if ((activity == null) || (activity.isFinishing()) || (alertDialog == null)) {
+                return;
+            }
+
+            // Get a handle for the IP addresses text view.
+            TextView ipAddressesTextView = alertDialog.findViewById(R.id.ip_addresses);
+
+            // Populate the IP addresses text view.
+            ipAddressesTextView.setText(ipAddresses);
+        }
+    }
 }
index c236d3a..73ad2ce 100644 (file)
@@ -20,7 +20,6 @@
 package com.stoutner.privacybrowser.dialogs;
 
 import android.annotation.SuppressLint;
-import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -28,7 +27,6 @@ import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -39,10 +37,6 @@ import android.widget.TextView;
 
 import com.stoutner.privacybrowser.activities.MainWebViewActivity;
 import com.stoutner.privacybrowser.R;
-
-import java.lang.ref.WeakReference;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.text.DateFormat;
 import java.util.Calendar;
 import java.util.Date;
@@ -52,10 +46,7 @@ import java.util.Date;
 public class ViewSslCertificateDialog extends DialogFragment {
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         // Get the activity's layout inflater.
-        LayoutInflater layoutInflater   = getActivity().getLayoutInflater();
-
-        // Create a drawable version of the favorite icon.
-        Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIconBitmap);
+        LayoutInflater layoutInflater = getActivity().getLayoutInflater();
 
         // Use a builder to create the alert dialog.
         AlertDialog.Builder dialogBuilder;
@@ -67,6 +58,9 @@ public class ViewSslCertificateDialog extends DialogFragment {
             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
         }
 
+        // Create a drawable version of the favorite icon.
+        Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIconBitmap);
+
         // Set the icon.
         dialogBuilder.setIcon(favoriteIconDrawable);
 
@@ -132,6 +126,7 @@ public class ViewSslCertificateDialog extends DialogFragment {
 
             // Setup the labels.
             String domainLabel = getString(R.string.domain_label) + "  ";
+            String ipAddressesLabel = getString(R.string.ip_addresses) + "  ";
             String cNameLabel = getString(R.string.common_name) + "  ";
             String oNameLabel = getString(R.string.organization) + "  ";
             String uNameLabel = getString(R.string.organizational_unit) + "  ";
@@ -144,9 +139,6 @@ public class ViewSslCertificateDialog extends DialogFragment {
             // Extract the domain name from the URI.
             String domainString = uri.getHost();
 
-            // Get the IP addresses.
-            new GetIpAddresses(getActivity(), alertDialog).execute(domainString);
-
             // Get the SSL certificate.
             SslCertificate sslCertificate = MainWebViewActivity.sslCertificate;
 
@@ -162,6 +154,7 @@ public class ViewSslCertificateDialog extends DialogFragment {
 
             // Create spannable string builders for each text view that needs multiple colors of text.
             SpannableStringBuilder domainStringBuilder = new SpannableStringBuilder(domainLabel + domainString);
+            SpannableStringBuilder ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + MainWebViewActivity.currentHostIpAddresses);
             SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCName);
             SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToOName);
             SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUName);
@@ -189,7 +182,7 @@ public class ViewSslCertificateDialog extends DialogFragment {
             // Remove the incorrect lint error that `.equals` might produce a NullPointerException.
             assert domainString != null;
 
-            // Formet the `domainString` and `issuedToCName` colors.
+            // Formet the domain string and issued to CName colors.
             if (domainString.equals(issuedToCName)) {  // `domainString` and `issuedToCName` match.
                 // Set the strings to be blue.
                 domainStringBuilder.setSpan(blueColorSpan, domainLabel.length(), domainStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
@@ -231,13 +224,15 @@ public class ViewSslCertificateDialog extends DialogFragment {
                 issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             }
 
-            // Set the issued to and issued by spans to display the certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            // Set the IP addresses, issued to, and issued by spans to display the certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
 
+            // Get the current date.
             Date currentDate = Calendar.getInstance().getTime();
 
             //  Format the start date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
@@ -256,7 +251,7 @@ public class ViewSslCertificateDialog extends DialogFragment {
 
             // Display the strings.
             domainTextView.setText(domainStringBuilder);
-            ipAddressesTextView.setText(getString(R.string.ip_addresses));
+            ipAddressesTextView.setText(ipAddressesStringBuilder);
             issuedToCNameTextView.setText(issuedToCNameStringBuilder);
             issuedToONameTextView.setText(issuedToONameStringBuilder);
             issuedToUNameTextView.setText(issuedToUNameStringBuilder);
@@ -266,101 +261,8 @@ public class ViewSslCertificateDialog extends DialogFragment {
             startDateTextView.setText(startDateStringBuilder);
             endDateTextView.setText(endDateStringBuilder);
 
-            // `onCreateDialog` requires the return of an `AlertDialog`.
+            // `onCreateDialog` requires the return of an alert dialog.
             return alertDialog;
         }
     }
-
-    // This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `String` contains the results.
-    private static class GetIpAddresses extends AsyncTask<String, Void, SpannableStringBuilder> {
-        // The weak references are used to determine if the activity or the alert dialog have disappeared while the AsyncTask is running.
-        private WeakReference<Activity> activityWeakReference;
-        private WeakReference<AlertDialog> alertDialogWeakReference;
-
-        GetIpAddresses(Activity activity, AlertDialog alertDialog) {
-            // Populate the weak references.
-            activityWeakReference = new WeakReference<>(activity);
-            alertDialogWeakReference = new WeakReference<>(alertDialog);
-        }
-
-        @Override
-        protected SpannableStringBuilder doInBackground(String... domainName) {
-            // Get handles for the activity and the alert dialog.
-            Activity activity = activityWeakReference.get();
-            AlertDialog alertDialog = alertDialogWeakReference.get();
-
-            // Abort if the activity or the dialog is gone.
-            if ((activity == null) || (activity.isFinishing()) || (alertDialog == null)) {
-                return new SpannableStringBuilder();
-            }
-
-            // Initialize an IP address string builder.
-            StringBuilder ipAddresses = new StringBuilder();
-
-            // Get an array with the IP addresses for the host.
-            try {
-                // Get an array with all the IP addresses for the domain.
-                InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
-
-                // Add each IP address to the string builder.
-                for (InetAddress inetAddress : inetAddressesArray) {
-                    if (ipAddresses.length() == 0) {  // This is the first IP address.
-                        // Add the IP Address to the string builder.
-                        ipAddresses.append(inetAddress.getHostAddress());
-                    } else {  // This is not the first IP address.
-                        // Add a line break to the string builder first.
-                        ipAddresses.append("\n");
-
-                        // Add the IP address to the string builder.
-                        ipAddresses.append(inetAddress.getHostAddress());
-                    }
-                }
-            } catch (UnknownHostException exception) {
-                // Do nothing.
-            }
-
-            // Set the label.
-            String ipAddressesLabel = activity.getString(R.string.ip_addresses) + "  ";
-
-            // Create a spannable string builder.
-            SpannableStringBuilder ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + ipAddresses);
-
-            // Create a blue foreground color span.
-            ForegroundColorSpan blueColorSpan;
-
-            // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until the minimum API >= 23.
-            if (MainWebViewActivity.darkTheme) {
-                //noinspection deprecation
-                blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.blue_400));
-            } else {
-                //noinspection deprecation
-                blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.blue_700));
-            }
-
-            // Set the string builder to display the certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
-            ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-
-            // Return the formatted string.
-            return ipAddressesStringBuilder;
-        }
-
-        // `onPostExecute()` operates on the UI thread.
-        @Override
-        protected void onPostExecute(SpannableStringBuilder ipAddresses) {
-            // Get handles for the activity and the alert dialog.
-            Activity activity = activityWeakReference.get();
-            AlertDialog alertDialog = alertDialogWeakReference.get();
-
-            // Abort if the activity or the alert dialog is gone.
-            if ((activity == null) || (activity.isFinishing()) || (alertDialog == null)) {
-                return;
-            }
-
-            // Get a handle for the IP addresses text view.
-            TextView ipAddressesTextView = alertDialog.findViewById(R.id.ip_addresses);
-
-            // Populate the IP addresses text view.
-            ipAddressesTextView.setText(ipAddresses);
-        }
-    }
 }
\ No newline at end of file
index aea9bf3..614bb37 100644 (file)
@@ -79,13 +79,12 @@ public class DomainSettingsFragment extends Fragment {
     }
 
     // The deprecated `getDrawable()` must be used until the minimum API >= 21.
-    @SuppressWarnings("deprecation")
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         // 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` and the `Resources`.
+        // Get a handle for the context and the resources.
         Context context = getContext();
         final Resources resources = getResources();
 
@@ -102,45 +101,45 @@ public class DomainSettingsFragment extends Fragment {
 
         // Get handles for the views in the fragment.
         final EditText domainNameEditText = domainSettingsView.findViewById(R.id.domain_settings_name_edittext);
-        final Switch javaScriptEnabledSwitch = domainSettingsView.findViewById(R.id.domain_settings_javascript_switch);
-        final ImageView javaScriptImageView = domainSettingsView.findViewById(R.id.domain_settings_javascript_imageview);
-        Switch firstPartyCookiesEnabledSwitch = domainSettingsView.findViewById(R.id.domain_settings_first_party_cookies_switch);
-        final ImageView firstPartyCookiesImageView = domainSettingsView.findViewById(R.id.domain_settings_first_party_cookies_imageview);
-        LinearLayout thirdPartyCookiesLinearLayout = domainSettingsView.findViewById(R.id.domain_settings_third_party_cookies_linearlayout);
-        final Switch thirdPartyCookiesEnabledSwitch = domainSettingsView.findViewById(R.id.domain_settings_third_party_cookies_switch);
-        final ImageView thirdPartyCookiesImageView = domainSettingsView.findViewById(R.id.domain_settings_third_party_cookies_imageview);
-        final Switch domStorageEnabledSwitch = domainSettingsView.findViewById(R.id.domain_settings_dom_storage_switch);
-        final ImageView domStorageImageView = domainSettingsView.findViewById(R.id.domain_settings_dom_storage_imageview);
-        Switch formDataEnabledSwitch = domainSettingsView.findViewById(R.id.domain_settings_form_data_switch);  // The form data views can be remove once the minimum API >= 26.
-        final ImageView formDataImageView = domainSettingsView.findViewById(R.id.domain_settings_form_data_imageview);  // The form data views can be remove once the minimum API >= 26.
-        Switch easyListSwitch = domainSettingsView.findViewById(R.id.domain_settings_easylist_switch);
-        ImageView easyListImageView = domainSettingsView.findViewById(R.id.domain_settings_easylist_imageview);
-        Switch easyPrivacySwitch = domainSettingsView.findViewById(R.id.domain_settings_easyprivacy_switch);
-        ImageView easyPrivacyImageView = domainSettingsView.findViewById(R.id.domain_settings_easyprivacy_imageview);
-        Switch fanboysAnnoyanceListSwitch = domainSettingsView.findViewById(R.id.domain_settings_fanboys_annoyance_list_switch);
-        ImageView fanboysAnnoyanceListImageView = domainSettingsView.findViewById(R.id.domain_settings_fanboys_annoyance_list_imageview);
-        Switch fanboysSocialBlockingListSwitch = domainSettingsView.findViewById(R.id.domain_settings_fanboys_social_blocking_list_switch);
-        ImageView fanboysSocialBlockingListImageView = domainSettingsView.findViewById(R.id.domain_settings_fanboys_social_blocking_list_imageview);
-        Switch ultraPrivacySwitch = domainSettingsView.findViewById(R.id.domain_settings_ultraprivacy_switch);
-        ImageView ultraPrivacyImageView = domainSettingsView.findViewById(R.id.domain_settings_ultraprivacy_imageview);
-        Switch blockAllThirdPartyRequestsSwitch = domainSettingsView.findViewById(R.id.domain_settings_block_all_third_party_requests_switch);
-        ImageView blockAllThirdPartyRequestsImageView = domainSettingsView.findViewById(R.id.domain_settings_block_all_third_party_requests_imageview);
-        final Spinner userAgentSpinner = domainSettingsView.findViewById(R.id.domain_settings_user_agent_spinner);
-        final TextView userAgentTextView = domainSettingsView.findViewById(R.id.domain_settings_user_agent_textview);
-        final EditText customUserAgentEditText = domainSettingsView.findViewById(R.id.domain_settings_custom_user_agent_edittext);
-        final Spinner fontSizeSpinner = domainSettingsView.findViewById(R.id.domain_settings_font_size_spinner);
-        final TextView fontSizeTextView = domainSettingsView.findViewById(R.id.domain_settings_font_size_textview);
-        final ImageView swipeToRefreshImageView = domainSettingsView.findViewById(R.id.domain_settings_swipe_to_refresh_imageview);
-        final Spinner swipeToRefreshSpinner = domainSettingsView.findViewById(R.id.domain_settings_swipe_to_refresh_spinner);
-        final TextView swipeToRefreshTextView = domainSettingsView.findViewById(R.id.domain_settings_swipe_to_refresh_textview);
-        final ImageView nightModeImageView = domainSettingsView.findViewById(R.id.domain_settings_night_mode_imageview);
-        final Spinner nightModeSpinner = domainSettingsView.findViewById(R.id.domain_settings_night_mode_spinner);
-        final TextView nightModeTextView = domainSettingsView.findViewById(R.id.domain_settings_night_mode_textview);
-        final ImageView displayWebpageImagesImageView = domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_imageview);
-        final Spinner displayWebpageImagesSpinner = domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_spinner);
-        final TextView displayImagesTextView = domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_textview);
-        final ImageView pinnedSslCertificateImageView = domainSettingsView.findViewById(R.id.domain_settings_pinned_ssl_certificate_imageview);
-        Switch pinnedSslCertificateSwitch = domainSettingsView.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
+        final Switch javaScriptEnabledSwitch = domainSettingsView.findViewById(R.id.javascript_switch);
+        final ImageView javaScriptImageView = domainSettingsView.findViewById(R.id.javascript_imageview);
+        Switch firstPartyCookiesEnabledSwitch = domainSettingsView.findViewById(R.id.first_party_cookies_switch);
+        final ImageView firstPartyCookiesImageView = domainSettingsView.findViewById(R.id.first_party_cookies_imageview);
+        LinearLayout thirdPartyCookiesLinearLayout = domainSettingsView.findViewById(R.id.third_party_cookies_linearlayout);
+        final Switch thirdPartyCookiesEnabledSwitch = domainSettingsView.findViewById(R.id.third_party_cookies_switch);
+        final ImageView thirdPartyCookiesImageView = domainSettingsView.findViewById(R.id.third_party_cookies_imageview);
+        final Switch domStorageEnabledSwitch = domainSettingsView.findViewById(R.id.dom_storage_switch);
+        final ImageView domStorageImageView = domainSettingsView.findViewById(R.id.dom_storage_imageview);
+        Switch formDataEnabledSwitch = domainSettingsView.findViewById(R.id.form_data_switch);  // The form data views can be remove once the minimum API >= 26.
+        final ImageView formDataImageView = domainSettingsView.findViewById(R.id.form_data_imageview);  // The form data views can be remove once the minimum API >= 26.
+        Switch easyListSwitch = domainSettingsView.findViewById(R.id.easylist_switch);
+        ImageView easyListImageView = domainSettingsView.findViewById(R.id.easylist_imageview);
+        Switch easyPrivacySwitch = domainSettingsView.findViewById(R.id.easyprivacy_switch);
+        ImageView easyPrivacyImageView = domainSettingsView.findViewById(R.id.easyprivacy_imageview);
+        Switch fanboysAnnoyanceListSwitch = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_switch);
+        ImageView fanboysAnnoyanceListImageView = domainSettingsView.findViewById(R.id.fanboys_annoyance_list_imageview);
+        Switch fanboysSocialBlockingListSwitch = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_switch);
+        ImageView fanboysSocialBlockingListImageView = domainSettingsView.findViewById(R.id.fanboys_social_blocking_list_imageview);
+        Switch ultraPrivacySwitch = domainSettingsView.findViewById(R.id.ultraprivacy_switch);
+        ImageView ultraPrivacyImageView = domainSettingsView.findViewById(R.id.ultraprivacy_imageview);
+        Switch blockAllThirdPartyRequestsSwitch = domainSettingsView.findViewById(R.id.block_all_third_party_requests_switch);
+        ImageView blockAllThirdPartyRequestsImageView = domainSettingsView.findViewById(R.id.block_all_third_party_requests_imageview);
+        final Spinner userAgentSpinner = domainSettingsView.findViewById(R.id.user_agent_spinner);
+        final TextView userAgentTextView = domainSettingsView.findViewById(R.id.user_agent_textview);
+        final EditText customUserAgentEditText = domainSettingsView.findViewById(R.id.custom_user_agent_edittext);
+        final Spinner fontSizeSpinner = domainSettingsView.findViewById(R.id.font_size_spinner);
+        final TextView fontSizeTextView = domainSettingsView.findViewById(R.id.font_size_textview);
+        final ImageView swipeToRefreshImageView = domainSettingsView.findViewById(R.id.swipe_to_refresh_imageview);
+        final Spinner swipeToRefreshSpinner = domainSettingsView.findViewById(R.id.swipe_to_refresh_spinner);
+        final TextView swipeToRefreshTextView = domainSettingsView.findViewById(R.id.swipe_to_refresh_textview);
+        final ImageView nightModeImageView = domainSettingsView.findViewById(R.id.night_mode_imageview);
+        final Spinner nightModeSpinner = domainSettingsView.findViewById(R.id.night_mode_spinner);
+        final TextView nightModeTextView = domainSettingsView.findViewById(R.id.night_mode_textview);
+        final ImageView displayWebpageImagesImageView = domainSettingsView.findViewById(R.id.display_webpage_images_imageview);
+        final Spinner displayWebpageImagesSpinner = domainSettingsView.findViewById(R.id.display_webpage_images_spinner);
+        final TextView displayImagesTextView = domainSettingsView.findViewById(R.id.display_webpage_images_textview);
+        final ImageView pinnedSslCertificateImageView = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_imageview);
+        Switch pinnedSslCertificateSwitch = domainSettingsView.findViewById(R.id.pinned_ssl_certificate_switch);
         final CardView savedSslCertificateCardView = domainSettingsView.findViewById(R.id.saved_ssl_certificate_cardview);
         LinearLayout savedSslCertificateLinearLayout = domainSettingsView.findViewById(R.id.saved_ssl_certificate_linearlayout);
         final RadioButton savedSslCertificateRadioButton = domainSettingsView.findViewById(R.id.saved_ssl_certificate_radiobutton);
@@ -164,9 +163,19 @@ public class DomainSettingsFragment extends Fragment {
         TextView currentWebsiteCertificateStartDateTextView = domainSettingsView.findViewById(R.id.current_website_certificate_start_date);
         TextView currentWebsiteCertificateEndDateTextView = domainSettingsView.findViewById(R.id.current_website_certificate_end_date);
         final TextView noCurrentWebsiteCertificateTextView = domainSettingsView.findViewById(R.id.no_current_website_certificate);
-
-        // Setup the SSL certificate labels.
-        final String cNameLabel = getString(R.string.common_name) + "  ";
+        ImageView pinnedIpAddressesImageView = domainSettingsView.findViewById(R.id.pinned_ip_addresses_imageview);
+        Switch pinnedIpAddressesSwitch = domainSettingsView.findViewById(R.id.pinned_ip_addresses_switch);
+        CardView savedIpAddressesCardView = domainSettingsView.findViewById(R.id.saved_ip_addresses_cardview);
+        LinearLayout savedIpAddressesLinearLayout = domainSettingsView.findViewById(R.id.saved_ip_addresses_linearlayout);
+        RadioButton savedIpAddressesRadioButton = domainSettingsView.findViewById(R.id.saved_ip_addresses_radiobutton);
+        TextView savedIpAddressesTextView = domainSettingsView.findViewById(R.id.saved_ip_addresses_textview);
+        CardView currentIpAddressesCardView = domainSettingsView.findViewById(R.id.current_ip_addresses_cardview);
+        LinearLayout currentIpAddressesLinearLayout = domainSettingsView.findViewById(R.id.current_ip_addresses_linearlayout);
+        RadioButton currentIpAddressesRadioButton = domainSettingsView.findViewById(R.id.current_ip_addresses_radiobutton);
+        TextView currentIpAddressesTextView = domainSettingsView.findViewById(R.id.current_ip_addresses_textview);
+
+        // Setup the pinned 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) + "  ";
@@ -178,11 +187,11 @@ public class DomainSettingsFragment extends Fragment {
         // Initialize the database handler.  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);
 
-        // Get the database `Cursor` for this ID and move it to the first row.
+        // Get the database cursor for this ID and move it to the first row.
         Cursor domainCursor = domainsDatabaseHelper.getCursorForId(databaseId);
         domainCursor.moveToFirst();
 
-        // Save the `Cursor` entries as variables.
+        // Save the cursor entries as variables.
         String domainNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
         final int javaScriptEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT));
         int firstPartyCookiesEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES));
@@ -201,12 +210,14 @@ public class DomainSettingsFragment extends Fragment {
         int nightModeInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
         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 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));
+        int pinnedIpAddressesInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES));
+        String savedIpAddresses = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
 
         // Initialize the saved SSL certificate date variables.
         Date savedSslCertificateStartDate = null;
@@ -251,7 +262,7 @@ public class DomainSettingsFragment extends Fragment {
         SpannableStringBuilder savedSslCertificateIssuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + savedSslCertificateIssuedByONameString);
         SpannableStringBuilder savedSslCertificateIssuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + savedSslCertificateIssuedByUNameString);
 
-        // Initialize the `SpannableStringBuilders` for the SSL certificate dates.
+        // Initialize the spannable string builders for the SSL certificate dates.
         SpannableStringBuilder savedSslCertificateStartDateStringBuilder;
         SpannableStringBuilder savedSslCertificateEndDateStringBuilder;
 
@@ -268,19 +279,19 @@ public class DomainSettingsFragment extends Fragment {
             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 red foreground color span.  The deprecated `resources.getColor` must be used until the minimum API >= 23.
+        final ForegroundColorSpan redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
 
-        // Create a blue `ForegroundColorSpan`.
+        // Create a blue foreground color span.
         final ForegroundColorSpan blueColorSpan;
 
-        // Set `blueColorSpan` according to the theme.  We have to use the deprecated `getColor()` until API >= 23.
+        // Set the blue color span according to the theme.  The deprecated `resources.getColor` must be used until the minimum API >= 23.
         if (MainWebViewActivity.darkTheme) {
             //noinspection deprecation
-            blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400));
+            blueColorSpan = new ForegroundColorSpan(resources.getColor(R.color.blue_400));
         } else {
             //noinspection deprecation
-            blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
+            blueColorSpan = new ForegroundColorSpan(resources.getColor(R.color.blue_700));
         }
 
         // Set the domain name from the the database cursor.
@@ -343,7 +354,7 @@ public class DomainSettingsFragment extends Fragment {
             }
         });
 
-        // Create a `boolean` to track if night mode is enabled.
+        // Create a boolean to track if night mode is enabled.
         boolean nightModeEnabled = (nightModeInt == DomainsDatabaseHelper.NIGHT_MODE_ENABLED) || ((nightModeInt == DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT) && defaultNightMode);
 
         // Disable the JavaScript switch if night mode is enabled.
@@ -960,17 +971,17 @@ public class DomainSettingsFragment extends Fragment {
         // 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.
+        // Setup the string builders 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.
+        // 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.
+        // Format the issued to Common Name 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 {
@@ -991,7 +1002,7 @@ public class DomainSettingsFragment extends Fragment {
             savedSslCertificateEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), savedSslCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
         }
 
-        // Display the current website SSL certificate strings.
+        // Display the saved website SSL certificate strings.
         savedSslCertificateIssuedToCNameTextView.setText(savedSslCertificateIssuedToCNameStringBuilder);
         savedSslCertificateIssuedToONameTextView.setText(savedSslCertificateIssuedToONameStringBuilder);
         savedSslCertificateIssuedToUNameTextView.setText(savedSslCertificateIssuedToUNameStringBuilder);
@@ -1013,7 +1024,7 @@ public class DomainSettingsFragment extends Fragment {
             Date currentWebsiteCertificateStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
             Date currentWebsiteCertificateEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
 
-            // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text.
+            // Create a spannable string builder for each text view that needs multiple colors of text.
             SpannableStringBuilder currentWebsiteCertificateIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentWebsiteCertificateIssuedToCNameString);
             SpannableStringBuilder currentWebsiteCertificateIssuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentWebsiteCertificateIssuedToONameString);
             SpannableStringBuilder currentWebsiteCertificateIssuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentWebsiteCertificateIssuedToUNameString);
@@ -1025,17 +1036,17 @@ public class DomainSettingsFragment extends Fragment {
             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.
+            // Setup the string builders 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.
+            // 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.
+            // Format the issued to Common Name 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 {
@@ -1067,8 +1078,8 @@ public class DomainSettingsFragment extends Fragment {
             currentWebsiteCertificateEndDateTextView.setText(currentWebsiteCertificateEndDateStringBuilder);
         }
 
-        // Set the initial display status for the SSL certificates.
-        if (pinnedSslCertificateSwitch.isChecked()) {
+        // Set the initial display status of the SSL certificates card views.
+        if (pinnedSslCertificateSwitch.isChecked()) {  // An SSL certificate is pinned.
             // Set the visibility of the saved SSL certificate.
             if (savedSslCertificateIssuedToCNameString == null) {
                 savedSslCertificateCardView.setVisibility(View.GONE);
@@ -1077,13 +1088,13 @@ public class DomainSettingsFragment extends Fragment {
             }
 
             // Set the visibility of the current website SSL certificate.
-            if (currentWebsiteSslCertificate == null) {
+            if (currentWebsiteSslCertificate == null) {  // There is no current SSL certificate.
                 // Hide the SSL certificate.
                 currentWebsiteCertificateCardView.setVisibility(View.GONE);
 
                 // Show the instruction.
                 noCurrentWebsiteCertificateTextView.setVisibility(View.VISIBLE);
-            } else {
+            } else {  // There is a current SSL certificate.
                 // Show the SSL certificate.
                 currentWebsiteCertificateCardView.setVisibility(View.VISIBLE);
 
@@ -1091,7 +1102,7 @@ public class DomainSettingsFragment extends Fragment {
                 noCurrentWebsiteCertificateTextView.setVisibility(View.GONE);
             }
 
-            // Set the status of the radio buttons.
+            // Set the status of the radio buttons and the card view backgrounds.
             if (savedSslCertificateCardView.getVisibility() == View.VISIBLE) {  // The saved SSL certificate is displayed.
                 // Check the saved SSL certificate radio button.
                 savedSslCertificateRadioButton.setChecked(true);
@@ -1111,19 +1122,12 @@ public class DomainSettingsFragment extends Fragment {
 
                 // Uncheck the saved SSL certificate radio button.
                 savedSslCertificateRadioButton.setChecked(false);
-
-                // Darken the background of the saved SSL certificate linear layout according to the theme.
-                if (MainWebViewActivity.darkTheme) {
-                    savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_33);
-                } else {
-                    savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11);
-                }
             } else {  // Neither SSL certificate is visible.
                 // Uncheck both radio buttons.
                 savedSslCertificateRadioButton.setChecked(false);
                 currentWebsiteCertificateRadioButton.setChecked(false);
             }
-        } else {  // `pinnedSslCertificateSwitch` is not checked.
+        } else {  // An SSL certificate is not pinned.
             // Hide the SSl certificates and instructions.
             savedSslCertificateCardView.setVisibility(View.GONE);
             currentWebsiteCertificateCardView.setVisibility(View.GONE);
@@ -1134,6 +1138,76 @@ public class DomainSettingsFragment extends Fragment {
             currentWebsiteCertificateRadioButton.setChecked(false);
         }
 
+        // Set the pinned IP addresses icon.
+        if (pinnedIpAddressesInt == 1) {  // Pinned IP addresses is enabled.  Once the minimum API >= 21 a selector can be sued as the tint mode instead of specifying different icons.
+            // Check the switch.
+            pinnedIpAddressesSwitch.setChecked(true);
+
+            // Set the icon according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_dark));
+            } else {
+                pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_light));
+            }
+        } else {  // Pinned IP Addresses is disabled.
+            // Uncheck the switch.
+            pinnedIpAddressesSwitch.setChecked(false);
+
+            // Set the icon according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_dark));
+            } else {
+                pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_light));
+            }
+        }
+
+        // Populate the saved and current IP addresses.
+        savedIpAddressesTextView.setText(savedIpAddresses);
+        currentIpAddressesTextView.setText(MainWebViewActivity.currentHostIpAddresses);
+
+        // Set the initial display status of the IP addresses card views.
+        if (pinnedIpAddressesSwitch.isChecked()) {  // IP addresses are pinned.
+            // Set the visibility of the saved IP addresses.
+            if (savedIpAddresses == null) {  // There are no saved IP addresses.
+                savedIpAddressesCardView.setVisibility(View.GONE);
+            } else {  // There are saved IP addresses.
+                savedIpAddressesCardView.setVisibility(View.VISIBLE);
+            }
+
+            // Set the visibility of the current IP addresses.
+            currentIpAddressesCardView.setVisibility(View.VISIBLE);
+
+            // Set the status of the radio buttons and the card view backgrounds.
+            if (savedIpAddressesCardView.getVisibility() == View.VISIBLE) {  // The saved IP addresses are displayed.
+                // Check the saved IP addresses radio button.
+                savedIpAddressesRadioButton.setChecked(true);
+
+                // Uncheck the current IP addresses radio button.
+                currentIpAddressesRadioButton.setChecked(false);
+
+                // Darken the background of the current IP addresses linear layout according to the theme.
+                if (MainWebViewActivity.darkTheme) {
+                    currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33);
+                } else {
+                    currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11);
+                }
+            } else {  // The saved IP addresses are hidden.
+                // Check the current IP addresses radio button.
+                currentIpAddressesRadioButton.setChecked(true);
+
+                // Uncheck the saved IP addresses radio button.
+                savedIpAddressesRadioButton.setChecked(false);
+            }
+        } else {  // IP addresses are not pinned.
+            // Hide the IP addresses card views.
+            savedIpAddressesCardView.setVisibility(View.GONE);
+            currentIpAddressesCardView.setVisibility(View.GONE);
+
+            // Uncheck the radio buttons.
+            savedIpAddressesRadioButton.setChecked(false);
+            currentIpAddressesRadioButton.setChecked(false);
+        }
+
 
         // Set the JavaScript switch listener.
         javaScriptEnabledSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
@@ -1731,8 +1805,8 @@ public class DomainSettingsFragment extends Fragment {
         
         // Set the pinned SSL certificate switch listener.
         pinnedSslCertificateSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
-            // Update the icon
-            if (isChecked) {  // Pinned SSL certificate is enabled.
+            // Update the icon.
+            if (isChecked) {  // SSL certificate pinning is enabled.
                 // Set the icon according to the theme.
                 if (MainWebViewActivity.darkTheme) {
                     pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_dark));
@@ -1779,6 +1853,9 @@ public class DomainSettingsFragment extends Fragment {
                     } else {
                         currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11);
                     }
+
+                    // Scroll to the current website SSL certificate card.
+                    savedSslCertificateCardView.getParent().requestChildFocus(savedSslCertificateCardView, savedSslCertificateCardView);
                 } else if (currentWebsiteCertificateCardView.getVisibility() == View.VISIBLE) {  // The saved SSL certificate is hidden but the current website SSL certificate is visible.
                     // Check the current website SSL certificate radio button.
                     currentWebsiteCertificateRadioButton.setChecked(true);
@@ -1795,12 +1872,18 @@ public class DomainSettingsFragment extends Fragment {
                     } else {
                         savedSslCertificateLinearLayout.setBackgroundResource(R.color.black_translucent_11);
                     }
+
+                    // Scroll to the current website SSL certificate card.
+                    currentWebsiteCertificateCardView.getParent().requestChildFocus(currentWebsiteCertificateCardView, currentWebsiteCertificateCardView);
                 } else {  // Neither SSL certificate is visible.
                     // Uncheck both radio buttons.
                     savedSslCertificateRadioButton.setChecked(false);
                     currentWebsiteCertificateRadioButton.setChecked(false);
+
+                    // Scroll to the current website SSL certificate card.
+                    noCurrentWebsiteCertificateTextView.getParent().requestChildFocus(noCurrentWebsiteCertificateTextView, noCurrentWebsiteCertificateTextView);
                 }
-            } else {  // Pinned SSL certificate is disabled.
+            } else {  // SSL certificate pinning is disabled.
                 // Set the icon according to the theme.
                 if (MainWebViewActivity.darkTheme) {
                     pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_dark));
@@ -1819,7 +1902,7 @@ public class DomainSettingsFragment extends Fragment {
             }
         });
 
-        savedSslCertificateCardView.setOnClickListener((View v) -> {
+        savedSslCertificateCardView.setOnClickListener((View view) -> {
             // Check the saved SSL certificate radio button.
             savedSslCertificateRadioButton.setChecked(true);
 
@@ -1837,7 +1920,7 @@ public class DomainSettingsFragment extends Fragment {
             }
         });
 
-        savedSslCertificateRadioButton.setOnClickListener((View v) -> {
+        savedSslCertificateRadioButton.setOnClickListener((View view) -> {
             // Check the saved SSL certificate radio button.
             savedSslCertificateRadioButton.setChecked(true);
 
@@ -1855,7 +1938,7 @@ public class DomainSettingsFragment extends Fragment {
             }
         });
 
-        currentWebsiteCertificateCardView.setOnClickListener((View v) -> {
+        currentWebsiteCertificateCardView.setOnClickListener((View view) -> {
             // Check the current website SSL certificate radio button.
             currentWebsiteCertificateRadioButton.setChecked(true);
 
@@ -1873,7 +1956,7 @@ public class DomainSettingsFragment extends Fragment {
             }
         });
 
-        currentWebsiteCertificateRadioButton.setOnClickListener((View v) -> {
+        currentWebsiteCertificateRadioButton.setOnClickListener((View view) -> {
             // Check the current website SSL certificate radio button.
             currentWebsiteCertificateRadioButton.setChecked(true);
 
@@ -1891,6 +1974,154 @@ public class DomainSettingsFragment extends Fragment {
             }
         });
 
+        // Set the pinned IP addresses switch listener.
+        pinnedIpAddressesSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
+            // Update the icon.
+            if (isChecked) {  // IP addresses pinning is enabled.
+                // Set the icon according to the theme.
+                if (MainWebViewActivity.darkTheme) {
+                    pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_dark));
+                } else {
+                    pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_light));
+                }
+
+                // Update the visibility of the saved IP addresses card view.
+                if (savedIpAddresses == null) {  // There are no saved IP addresses.
+                    savedIpAddressesCardView.setVisibility(View.GONE);
+                } else {  // There are saved IP addresses.
+                    savedIpAddressesCardView.setVisibility(View.VISIBLE);
+                }
+
+                // Show the current IP addresses card view.
+                currentIpAddressesCardView.setVisibility(View.VISIBLE);
+
+                // Set the status of the radio buttons.
+                if (savedIpAddressesCardView.getVisibility() == View.VISIBLE) {  // The saved IP addresses are visible.
+                    // Check the saved IP addresses radio button.
+                    savedIpAddressesRadioButton.setChecked(true);
+
+                    // Uncheck the current IP addresses radio button.
+                    currentIpAddressesRadioButton.setChecked(false);
+
+                    // Set the background of the saved IP addresses linear layout to be transparent.
+                    savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent);
+
+                    // Darken the background of the current IP addresses linear layout according to the theme.
+                    if (MainWebViewActivity.darkTheme) {
+                        currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33);
+                    } else {
+                        currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11);
+                    }
+                } else {  // The saved IP addresses are not visible.
+                    // Check the current IP addresses radio button.
+                    currentIpAddressesRadioButton.setChecked(true);
+
+                    // Uncheck the saved IP addresses radio button.
+                    savedIpAddressesRadioButton.setChecked(false);
+
+                    // Set the background of the current IP addresses linear layout to be transparent.
+                    currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent);
+
+                    // Darken the background of the saved IP addresses linear layout according to the theme.
+                    if (MainWebViewActivity.darkTheme) {
+                        savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33);
+                    } else {
+                        savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11);
+                    }
+                }
+
+                // Scroll to the bottom of the card views.
+                currentIpAddressesCardView.getParent().requestChildFocus(currentIpAddressesCardView, currentIpAddressesCardView);
+            } else {  // IP addresses pinning is disabled.
+                // Set the icon according to the theme.
+                if (MainWebViewActivity.darkTheme) {
+                    pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_dark));
+                } else {
+                    pinnedIpAddressesImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_light));
+                }
+
+                // Hide the IP addresses card views.
+                savedIpAddressesCardView.setVisibility(View.GONE);
+                currentIpAddressesCardView.setVisibility(View.GONE);
+
+                // Uncheck the radio buttons.
+                savedIpAddressesRadioButton.setChecked(false);
+                currentIpAddressesRadioButton.setChecked(false);
+            }
+        });
+
+        savedIpAddressesCardView.setOnClickListener((View view) -> {
+            // Check the saved IP addresses radio button.
+            savedIpAddressesRadioButton.setChecked(true);
+
+            // Uncheck the current website IP addresses radio button.
+            currentIpAddressesRadioButton.setChecked(false);
+
+            // Set the background of the saved IP addresses linear layout to be transparent.
+            savedIpAddressesLinearLayout.setBackgroundResource(R.color.transparent);
+
+            // Darken the background of the current IP addresses linear layout according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33);
+            } else {
+                currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11);
+            }
+        });
+
+        savedIpAddressesRadioButton.setOnClickListener((View view) -> {
+            // Check the saved IP addresses radio button.
+            savedIpAddressesRadioButton.setChecked(true);
+
+            // Uncheck the current website IP addresses radio button.
+            currentIpAddressesRadioButton.setChecked(false);
+
+            // Set the background of the saved IP addresses linear layout to be transparent.
+            savedIpAddressesLinearLayout.setBackgroundResource(R.color.transparent);
+
+            // Darken the background of the current IP addresses linear layout according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33);
+            } else {
+                currentIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11);
+            }
+        });
+
+        currentIpAddressesCardView.setOnClickListener((View view) -> {
+            // Check the current IP addresses radio button.
+            currentIpAddressesRadioButton.setChecked(true);
+
+            // Uncheck the saved IP addresses radio button.
+            savedIpAddressesRadioButton.setChecked(false);
+
+            // Set the background of the current IP addresses linear layout to be transparent.
+            currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent);
+
+            // Darken the background of the saved IP addresses linear layout according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33);
+            } else {
+                savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11);
+            }
+        });
+
+        currentIpAddressesRadioButton.setOnClickListener((View view) -> {
+            // Check the current IP addresses radio button.
+            currentIpAddressesRadioButton.setChecked(true);
+
+            // Uncheck the saved IP addresses radio button.
+            savedIpAddressesRadioButton.setChecked(false);
+
+            // Set the background of the current IP addresses linear layout to be transparent.
+            currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent);
+
+            // Darken the background of the saved IP addresses linear layout according to the theme.
+            if (MainWebViewActivity.darkTheme) {
+                savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_33);
+            } else {
+                savedIpAddressesLinearLayout.setBackgroundResource(R.color.black_translucent_11);
+            }
+        });
+
         return domainSettingsView;
     }
 
index 9494d99..b74ca52 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2017-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -28,7 +28,7 @@ import android.database.sqlite.SQLiteOpenHelper;
 import android.preference.PreferenceManager;
 
 public class DomainsDatabaseHelper extends SQLiteOpenHelper {
-    private static final int SCHEMA_VERSION = 8;
+    private static final int SCHEMA_VERSION = 9;
     static final String DOMAINS_DATABASE = "domains.db";
     static final String DOMAINS_TABLE = "domains";
 
@@ -59,6 +59,8 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
     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 String PINNED_IP_ADDRESSES = "pinned_ip_addresses";
+    public static final String IP_ADDRESSES = "ip_addresses";
 
     // Swipe to refresh constants.
     public static final int SWIPE_TO_REFRESH_SYSTEM_DEFAULT = 0;
@@ -102,7 +104,9 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
             SSL_ISSUED_BY_ORGANIZATION + " TEXT, " +
             SSL_ISSUED_BY_ORGANIZATIONAL_UNIT + " TEXT, " +
             SSL_START_DATE + " INTEGER, " +
-            SSL_END_DATE + " INTEGER)";
+            SSL_END_DATE + " INTEGER, " +
+            PINNED_IP_ADDRESSES + " BOOLEAN, " +
+            IP_ADDRESSES + " TEXT)";
 
     private final Context appContext;
 
@@ -209,6 +213,12 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
 
                 // Enable it for all existing rows.
                 domainsDatabase.execSQL("UPDATE " + DOMAINS_TABLE + " SET " + ENABLE_ULTRAPRIVACY + " = " + 1);
+
+            // Upgrade from schema version 8.
+            case 8:
+                // Add the Pinned IP Addresses columns.
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + PINNED_IP_ADDRESSES + " BOOLEAN");
+                domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + IP_ADDRESSES + " TEXT");
         }
     }
 
@@ -331,10 +341,9 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
         domainsDatabase.close();
     }
 
-    public void updateDomainExceptCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled,
-                                              boolean formDataEnabled, boolean easyListEnabled, boolean easyPrivacyEnabled, boolean fanboysAnnoyanceEnabled, boolean fanboysSocialBlockingEnabled,
-                                              boolean ultraPrivacyEnabled, boolean blockAllThirdPartyRequests, String userAgent, int fontSize, int swipeToRefresh, int nightMode, int displayImages,
-                                              boolean pinnedSslCertificate) {
+    public void updateDomain(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled,
+                             boolean easyListEnabled, boolean easyPrivacyEnabled, boolean fanboysAnnoyanceEnabled, boolean fanboysSocialBlockingEnabled, boolean ultraPrivacyEnabled,
+                             boolean blockAllThirdPartyRequests, String userAgent, int fontSize, int swipeToRefresh, int nightMode, int displayImages, boolean pinnedSslCertificate, boolean pinnedIpAddresses) {
 
         // Store the domain data in a `ContentValues`.
         ContentValues domainContentValues = new ContentValues();
@@ -358,6 +367,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
         domainContentValues.put(NIGHT_MODE, nightMode);
         domainContentValues.put(DISPLAY_IMAGES, displayImages);
         domainContentValues.put(PINNED_SSL_CERTIFICATE, pinnedSslCertificate);
+        domainContentValues.put(PINNED_IP_ADDRESSES, pinnedIpAddresses);
 
         // Get a writable database handle.
         SQLiteDatabase domainsDatabase = this.getWritableDatabase();
@@ -369,73 +379,44 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
         domainsDatabase.close();
     }
 
-    public void updateDomainWithCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled,
-                                            boolean formDataEnabled, boolean easyListEnabled, boolean easyPrivacyEnabled, boolean fanboysAnnoyanceEnabled, boolean fanboysSocialBlockingEnabled,
-                                            boolean ultraPrivacyEnabled, boolean blockAllThirdPartyRequests, String userAgent, int fontSize, int swipeToRefresh, int nightMode, int displayImages,
-                                            boolean pinnedSslCertificate, String sslIssuedToCommonName, String sslIssuedToOrganization, String sslIssuedToOrganizationalUnit, String sslIssuedByCommonName,
-                                            String sslIssuedByOrganization, String sslIssuedByOrganizationalUnit, long sslStartDate, long sslEndDate) {
+    public void updatePinnedSslCertificate(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();
+        // Store the pinned SSL certificate in a content values.
+        ContentValues pinnedSslCertificateContentValues = 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);  // Form data can be removed once the minimum API >= 26.
-        domainContentValues.put(ENABLE_EASYLIST, easyListEnabled);
-        domainContentValues.put(ENABLE_EASYPRIVACY, easyPrivacyEnabled);
-        domainContentValues.put(ENABLE_FANBOYS_ANNOYANCE_LIST, fanboysAnnoyanceEnabled);
-        domainContentValues.put(ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST, fanboysSocialBlockingEnabled);
-        domainContentValues.put(ENABLE_ULTRAPRIVACY, ultraPrivacyEnabled);
-        domainContentValues.put(BLOCK_ALL_THIRD_PARTY_REQUESTS, blockAllThirdPartyRequests);
-        domainContentValues.put(USER_AGENT, userAgent);
-        domainContentValues.put(FONT_SIZE, fontSize);
-        domainContentValues.put(SWIPE_TO_REFRESH, swipeToRefresh);
-        domainContentValues.put(NIGHT_MODE, nightMode);
-        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);
+        // Add entries for each field in the certificate.
+        pinnedSslCertificateContentValues.put(SSL_ISSUED_TO_COMMON_NAME, sslIssuedToCommonName);
+        pinnedSslCertificateContentValues.put(SSL_ISSUED_TO_ORGANIZATION, sslIssuedToOrganization);
+        pinnedSslCertificateContentValues.put(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, sslIssuedToOrganizationalUnit);
+        pinnedSslCertificateContentValues.put(SSL_ISSUED_BY_COMMON_NAME, sslIssuedByCommonName);
+        pinnedSslCertificateContentValues.put(SSL_ISSUED_BY_ORGANIZATION, sslIssuedByOrganization);
+        pinnedSslCertificateContentValues.put(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, sslIssuedByOrganizationalUnit);
+        pinnedSslCertificateContentValues.put(SSL_START_DATE, sslStartDate);
+        pinnedSslCertificateContentValues.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);
+        // Update the row for database ID.
+        domainsDatabase.update(DOMAINS_TABLE, pinnedSslCertificateContentValues, _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();
+    public void updatePinnedIpAddresses(int databaseId, String ipAddresses) {
+        // Store the pinned IP addresses in a content values.
+        ContentValues pinnedIpAddressesContentValues = 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);
+        // Add the IP addresses to the content values.
+        pinnedIpAddressesContentValues.put(IP_ADDRESSES, ipAddresses);
 
         // 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);
+        // Update the row for the database ID.
+        domainsDatabase.update(DOMAINS_TABLE, pinnedIpAddressesContentValues, _ID + " = " + databaseId, null);
 
         // Close the database handle.
         domainsDatabase.close();
index bcec529..4f9d257 100644 (file)
@@ -38,7 +38,7 @@ public class ImportExportDatabaseHelper {
     public static final String EXPORT_SUCCESSFUL = "Export Successful";
     public static final String IMPORT_SUCCESSFUL = "Import Successful";
 
-    private static final int SCHEMA_VERSION = 3;
+    private static final int SCHEMA_VERSION = 4;
     private static final String PREFERENCES_TABLE = "preferences";
 
     // The preferences constants.
@@ -138,6 +138,8 @@ public class ImportExportDatabaseHelper {
                 domainsContentValues.put(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)));
                 domainsContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, domainsCursor.getLong(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
                 domainsContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, domainsCursor.getLong(domainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                domainsContentValues.put(DomainsDatabaseHelper.PINNED_IP_ADDRESSES, domainsCursor.getInt(domainsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)));
+                domainsContentValues.put(DomainsDatabaseHelper.IP_ADDRESSES, domainsCursor.getString(domainsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)));
 
                 // Insert the record into the export database.
                 exportDatabase.insert(DomainsDatabaseHelper.DOMAINS_TABLE, null, domainsContentValues);
@@ -328,7 +330,7 @@ public class ImportExportDatabaseHelper {
             // Create an integer to track the number of bytes read.
             int bytesRead;
 
-            // Copy the import file to the temporary import file.  Once API >= 26 `Files.copy` can be used instead.
+            // Copy the import file to the temporary import file.  Once the minimum API >= 26 `Files.copy` can be used instead.
             while ((bytesRead = importFileInputStream.read(transferByteArray)) > 0) {
                 temporaryImportFileOutputStream.write(transferByteArray, 0, bytesRead);
             }
@@ -341,7 +343,7 @@ public class ImportExportDatabaseHelper {
             // Get a handle for the shared preference.
             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
 
-            // Open the import database.  Once API >= 27 the file can be opened directly without using the string.
+            // Open the import database.  Once the minimum API >= 27 the file can be opened directly without using the string.
             SQLiteDatabase importDatabase = SQLiteDatabase.openDatabase(temporaryImportFileString, null, SQLiteDatabase.OPEN_READWRITE);
 
             // Get the database version.
@@ -388,6 +390,11 @@ public class ImportExportDatabaseHelper {
 
                         // Place the font size string in the new column.
                         importDatabase.execSQL("UPDATE " + PREFERENCES_TABLE + " SET " + FONT_SIZE + " = " + fontSize);
+
+                    case 3:
+                        // Add the Pinned IP Addresses columns.
+                        importDatabase.execSQL("ALTER TABLE " + DomainsDatabaseHelper.DOMAINS_TABLE + " ADD COLUMN " + DomainsDatabaseHelper.PINNED_IP_ADDRESSES + " BOOLEAN");
+                        importDatabase.execSQL("ALTER TABLE " + DomainsDatabaseHelper.DOMAINS_TABLE + " ADD COLUMN " + DomainsDatabaseHelper.IP_ADDRESSES + " TEXT");
                 }
             }
 
@@ -438,6 +445,8 @@ public class ImportExportDatabaseHelper {
                         importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)));
                 domainsContentValues.put(DomainsDatabaseHelper.SSL_START_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
                 domainsContentValues.put(DomainsDatabaseHelper.SSL_END_DATE, importDomainsCursor.getLong(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                domainsContentValues.put(DomainsDatabaseHelper.PINNED_IP_ADDRESSES, importDomainsCursor.getInt(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)));
+                domainsContentValues.put(DomainsDatabaseHelper.IP_ADDRESSES, importDomainsCursor.getString(importDomainsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)));
 
                 // Insert the record into the export database.
                 domainsDatabaseHelper.addDomain(domainsContentValues);
index c44dd7b..74e76de 100644 (file)
@@ -29,7 +29,6 @@
     android:descendantFocusability="beforeDescendants" >
 
     <LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
         android:layout_margin="12dp"
@@ -89,7 +88,7 @@
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_javascript_imageview"
+                android:id="@+id/javascript_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
@@ -98,7 +97,7 @@
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_javascript_switch"
+                android:id="@+id/javascript_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_first_party_cookies_imageview"
+                android:id="@+id/first_party_cookies_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_first_party_cookies_switch"
+                android:id="@+id/first_party_cookies_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
 
         <!-- Third-Party Cookies. -->
         <LinearLayout
-            android:id="@+id/domain_settings_third_party_cookies_linearlayout"
+            android:id="@+id/third_party_cookies_linearlayout"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_third_party_cookies_imageview"
+                android:id="@+id/third_party_cookies_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_third_party_cookies_switch"
+                android:id="@+id/third_party_cookies_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_dom_storage_imageview"
+                android:id="@+id/dom_storage_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_dom_storage_switch"
+                android:id="@+id/dom_storage_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_form_data_imageview"
+                android:id="@+id/form_data_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_form_data_switch"
+                android:id="@+id/form_data_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_easylist_imageview"
+                android:id="@+id/easylist_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_easylist_switch"
+                android:id="@+id/easylist_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_easyprivacy_imageview"
+                android:id="@+id/easyprivacy_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_easyprivacy_switch"
+                android:id="@+id/easyprivacy_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_fanboys_annoyance_list_imageview"
+                android:id="@+id/fanboys_annoyance_list_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_fanboys_annoyance_list_switch"
+                android:id="@+id/fanboys_annoyance_list_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_fanboys_social_blocking_list_imageview"
+                android:id="@+id/fanboys_social_blocking_list_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_fanboys_social_blocking_list_switch"
+                android:id="@+id/fanboys_social_blocking_list_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_ultraprivacy_imageview"
+                android:id="@+id/ultraprivacy_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_ultraprivacy_switch"
+                android:id="@+id/ultraprivacy_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
             android:orientation="horizontal" >
 
             <ImageView
-                android:id="@+id/domain_settings_block_all_third_party_requests_imageview"
+                android:id="@+id/block_all_third_party_requests_imageview"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:layout_marginTop="1dp"
                 tools:ignore="contentDescription" />
 
             <Switch
-                android:id="@+id/domain_settings_block_all_third_party_requests_switch"
+                android:id="@+id/block_all_third_party_requests_switch"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="8dp"
                     android:contentDescription="@string/user_agent" />
 
                 <Spinner
-                    android:id="@+id/domain_settings_user_agent_spinner"
+                    android:id="@+id/user_agent_spinner"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent" />
             </LinearLayout>
 
             <TextView
-                android:id="@+id/domain_settings_user_agent_textview"
+                android:id="@+id/user_agent_textview"
                 android:layout_height="match_parent"
                 android:layout_width="match_parent"
                 android:layout_marginStart="45dp"
                 android:textSize="13sp" />
 
             <EditText
-                android:id="@+id/domain_settings_custom_user_agent_edittext"
+                android:id="@+id/custom_user_agent_edittext"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginStart="40dp"
                     android:contentDescription="@string/font_size" />
 
                 <Spinner
-                    android:id="@+id/domain_settings_font_size_spinner"
+                    android:id="@+id/font_size_spinner"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent" />
             </LinearLayout>
 
             <TextView
-                android:id="@+id/domain_settings_font_size_textview"
+                android:id="@+id/font_size_textview"
                 android:layout_height="match_parent"
                 android:layout_width="match_parent"
                 android:layout_marginStart="45dp"
                 android:orientation="horizontal" >
 
                 <ImageView
-                    android:id="@+id/domain_settings_swipe_to_refresh_imageview"
+                    android:id="@+id/swipe_to_refresh_imageview"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:layout_marginTop="1dp"
                     android:contentDescription="@string/swipe_to_refresh" />
 
                 <Spinner
-                    android:id="@+id/domain_settings_swipe_to_refresh_spinner"
+                    android:id="@+id/swipe_to_refresh_spinner"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent" />
             </LinearLayout>
 
             <TextView
-                android:id="@+id/domain_settings_swipe_to_refresh_textview"
+                android:id="@+id/swipe_to_refresh_textview"
                 android:layout_height="match_parent"
                 android:layout_width="match_parent"
                 android:layout_marginStart="45dp"
                 android:orientation="horizontal" >
 
                 <ImageView
-                    android:id="@+id/domain_settings_night_mode_imageview"
+                    android:id="@+id/night_mode_imageview"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:layout_marginTop="1dp"
                     android:contentDescription="@string/night_mode" />
 
                 <Spinner
-                    android:id="@+id/domain_settings_night_mode_spinner"
+                    android:id="@+id/night_mode_spinner"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent" />
             </LinearLayout>
 
             <TextView
-                android:id="@+id/domain_settings_night_mode_textview"
+                android:id="@+id/night_mode_textview"
                 android:layout_height="match_parent"
                 android:layout_width="match_parent"
                 android:layout_marginStart="45dp"
                 android:orientation="horizontal" >
 
                 <ImageView
-                    android:id="@+id/domain_settings_display_webpage_images_imageview"
+                    android:id="@+id/display_webpage_images_imageview"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:layout_marginTop="1dp"
                     android:contentDescription="@string/display_webpage_images" />
 
                 <Spinner
-                    android:id="@+id/domain_settings_display_webpage_images_spinner"
+                    android:id="@+id/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:id="@+id/display_webpage_images_textview"
                 android:layout_height="match_parent"
                 android:layout_width="match_parent"
                 android:layout_marginStart="45dp"
             android:layout_width="match_parent"
             android:orientation="vertical"
             android:layout_marginTop="18dp"
-            android:layout_marginBottom="32dp" >
+            android:layout_marginBottom="18dp" >
 
             <!-- Switch -->
             <LinearLayout
                 android:orientation="horizontal" >
 
                 <ImageView
-                    android:id="@+id/domain_settings_pinned_ssl_certificate_imageview"
+                    android:id="@+id/pinned_ssl_certificate_imageview"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:layout_marginTop="1dp"
                     tools:ignore="contentDescription" />
 
                 <Switch
-                    android:id="@+id/domain_settings_pinned_ssl_certificate_switch"
+                    android:id="@+id/pinned_ssl_certificate_switch"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent"
                     android:layout_marginStart="8dp"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginTop="10dp"
-                android:layout_marginBottom="5dp"
                 android:layout_marginStart="10dp"
                 android:layout_marginEnd="10dp" >
 
                 android:id="@+id/current_website_certificate_cardview"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
-                android:layout_marginTop="5dp"
-                android:layout_marginBottom="10dp"
-                android:layout_marginStart="10dp"
-                android:layout_marginEnd="10dp" >
+                android:layout_margin="10dp" >
 
                 <LinearLayout
                     android:id="@+id/current_website_certificate_linearlayout"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:layout_marginTop="10dp"
+                android:layout_marginBottom="10dp"
                 android:layout_marginStart="40dp"
                 android:layout_marginEnd="40dp"
                 android:gravity="center_horizontal"
                 android:text="@string/load_an_encrypted_website" />
         </LinearLayout>
+
+        <!-- Pinned IP Addresses -->
+        <LinearLayout
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:orientation="vertical"
+            android:layout_marginTop="18dp"
+            android:layout_marginBottom="18dp" >
+
+            <!-- Switch -->
+            <LinearLayout
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:orientation="horizontal" >
+
+                <ImageView
+                    android:id="@+id/pinned_ip_addresses_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/pinned_ip_addresses_switch"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:layout_marginStart="8dp"
+                    android:text="@string/pinned_ip_addresses"
+                    android:textColor="?android:textColorPrimary"
+                    android:textSize="18sp" />
+            </LinearLayout>
+
+            <!-- Saved IP Addresses -->
+            <android.support.v7.widget.CardView
+                android:id="@+id/saved_ip_addresses_cardview"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:layout_marginTop="10dp"
+                android:layout_marginStart="10dp"
+                android:layout_marginEnd="10dp" >
+
+                <LinearLayout
+                    android:id="@+id/saved_ip_addresses_linearlayout"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:orientation="vertical"
+                    android:padding="10dp" >
+
+                    <RadioButton
+                        android:id="@+id/saved_ip_addresses_radiobutton"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:text="@string/saved_ip_addresses"
+                        android:textSize="17sp"
+                        android:textAllCaps="true"
+                        android:textStyle="bold"
+                        android:textColor="?android:textColorPrimary" />
+
+                    <TextView
+                        android:id="@+id/saved_ip_addresses_textview"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:layout_marginStart="32dp"
+                        android:textColor="?attr/aboutText" />
+                </LinearLayout>
+            </android.support.v7.widget.CardView>
+
+            <android.support.v7.widget.CardView
+                android:id="@+id/current_ip_addresses_cardview"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:layout_margin="10dp">
+
+                <LinearLayout
+                    android:id="@+id/current_ip_addresses_linearlayout"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:orientation="vertical"
+                    android:padding="10dp" >
+
+                    <RadioButton
+                        android:id="@+id/current_ip_addresses_radiobutton"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:text="@string/current_ip_addresses"
+                        android:textSize="17sp"
+                        android:textAllCaps="true"
+                        android:textStyle="bold"
+                        android:textColor="?android:textColorPrimary" />
+
+                    <TextView
+                        android:id="@+id/current_ip_addresses_textview"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:layout_marginStart="32dp"
+                        android:textColor="?attr/aboutText" />
+                </LinearLayout>
+            </android.support.v7.widget.CardView>
+        </LinearLayout>
     </LinearLayout>
 </ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/pinned_mismatch_linearlayout.xml b/app/src/main/res/layout/pinned_mismatch_linearlayout.xml
new file mode 100644 (file)
index 0000000..549e6f7
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2017-2019 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" />
+
+    <ScrollView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" >
+
+        <com.stoutner.privacybrowser.definitions.WrapVerticalContentViewPager
+            android:id="@+id/pinned_ssl_certificate_mismatch_viewpager"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+    </ScrollView>
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/pinned_mismatch_scrollview.xml b/app/src/main/res/layout/pinned_mismatch_scrollview.xml
new file mode 100644 (file)
index 0000000..b6b2a18
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright © 2017-2019 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"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:padding="10dp"
+    android:orientation="vertical" >
+
+    <!-- Domain. -->
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="5dp"
+        android:text="@string/domain"
+        android:textAllCaps="true"
+        android:textStyle="bold"
+        android:textColor="?attr/sslTitle" />
+
+    <TextView
+        android:id="@+id/domain_name"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content" />
+
+    <TextView
+        android:id="@+id/ip_addresses"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"/>
+
+
+    <!-- Issued To. -->
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="15dp"
+        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>
\ 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
deleted file mode 100644 (file)
index 73e465d..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2017-2018 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" />
-
-    <ScrollView
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content" >
-
-        <com.stoutner.privacybrowser.definitions.WrapVerticalContentViewPager
-            android:id="@+id/pinned_ssl_certificate_mismatch_viewpager"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content" />
-    </ScrollView>
-</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
deleted file mode 100644 (file)
index a8aa1a3..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2017-2018 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"
-    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>
\ No newline at end of file
index 6c95fbf..0a2bf6f 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2016-2017 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2016-2017,2019 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
             android:textColor="?attr/sslTitle" />
 
         <TextView
-            android:id="@+id/url_error_dialog"
+            android:id="@+id/url"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content" />
 
+        <TextView
+            android:id="@+id/ip_addresses"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/ip_addresses" />
+
         <!-- Issued To. -->
         <TextView
             android:layout_width="wrap_content"
             android:textColor="?attr/sslTitle" />
 
         <TextView
-            android:id="@+id/issued_to_cname_error_dialog"
+            android:id="@+id/issued_to_cname"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content" />
 
         <TextView
-            android:id="@+id/issued_to_oname_error_dialog"
+            android:id="@+id/issued_to_oname"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content" />
 
         <TextView
-            android:id="@+id/issued_to_uname_error_dialog"
+            android:id="@+id/issued_to_uname"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content"/>
 
             android:textColor="?attr/sslTitle"/>
 
         <TextView
-            android:id="@+id/issued_by_cname_error_dialog"
+            android:id="@+id/issued_by_cname"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content" />
 
         <TextView
-            android:id="@+id/issued_by_oname_error_dialog"
+            android:id="@+id/issued_by_oname"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content" />
 
         <TextView
-            android:id="@+id/issued_by_uname_error_dialog"
+            android:id="@+id/issued_by_uname"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content" />
 
             android:textColor="?attr/sslTitle"/>
 
         <TextView
-            android:id="@+id/start_date_error_dialog"
+            android:id="@+id/start_date"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content" />
 
         <TextView
-            android:id="@+id/end_date_error_dialog"
+            android:id="@+id/end_date"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content" />
     </LinearLayout>
index 8c26ff9..6391bf6 100644 (file)
                 android:checkable="true"
                 app:showAsAction="never" />
 
-            <item
-                android:id="@+id/print"
-                android:title="@string/print"
-                android:orderInCategory="970"
-                app:showAsAction="never" />
-
             <item
                 android:id="@+id/find_on_page"
                 android:title="@string/find_on_page"
-                android:orderInCategory="980"
+                android:orderInCategory="970"
                 app:showAsAction="never|collapseActionView" />
 
-            <item
-                android:id="@+id/add_to_homescreen"
-                android:title="@string/add_to_home_screen"
-                android:orderInCategory="990"
-                app:showAsAction="never" />
-
             <item
                 android:id="@+id/view_source"
                 android:title="@string/view_source"
-                android:orderInCategory="999"
+                android:orderInCategory="980"
                 app:showAsAction="never" />
         </menu>
     </item>
                 android:orderInCategory="1010"
                 app:showAsAction="never" />
 
+            <item
+                android:id="@+id/print"
+                android:title="@string/print"
+                android:orderInCategory="1020"
+                app:showAsAction="never" />
+
             <item
                 android:id="@+id/open_with_app"
                 android:title="@string/open_with_app"
-                android:orderInCategory="1020"
+                android:orderInCategory="1030"
                 app:showAsAction="never" />
 
             <item
                 android:id="@+id/open_with_browser"
                 android:title="@string/open_with_browser"
-                android:orderInCategory="1030"
+                android:orderInCategory="1040"
+                app:showAsAction="never" />
+
+            <item
+                android:id="@+id/add_to_homescreen"
+                android:title="@string/add_to_home_screen"
+                android:orderInCategory="1050"
                 app:showAsAction="never" />
         </menu>
     </item>
index 174ef2f..583a947 100644 (file)
     <string name="url">URL</string>
     <string name="url_label">URL:</string>
 
-    <!-- Pinned SSL Certificate Mismatch. -->
-    <string name="update_ssl">SSL aktualisieren</string>
-    <string name="ssl_certificate_mismatch">SSL-Zertifikat Mismatch</string>
-    <string name="current_ssl">Aktuelles SSL</string>
-    <string name="pinned_ssl">Pinned SSL</string>
+    <!-- Pinned Mismatch. -->
+    <string name="update">Aktualisieren</string>
+    <string name="current">Aktuelles</string>
 
     <!-- HTTP Authentication. -->
     <string name="http_authentication">HTTP-Authentifizierung</string>
         <item>Grafiken deaktiviert</item>
     </string-array>
     <string name="pinned_ssl_certificate">SSL-Zertifikat verankern</string>
-    <string name="saved_ssl_certificate">Gespeicherte SSL-Zertifikate</string>
-    <string name="current_website_ssl_certificate">SSL-Zertifikat der aktuellen Webseite</string>
-    <string name="load_an_encrypted_website">Zuerst verschlüsselte Webseite laden...</string>
+        <string name="saved_ssl_certificate">Gespeicherte SSL-Zertifikate</string>
+        <string name="current_website_ssl_certificate">SSL-Zertifikat der aktuellen Webseite</string>
+        <string name="load_an_encrypted_website">Zuerst verschlüsselte Webseite laden...</string>
 
     <!-- Guide. -->
     <string name="privacy_browser_guide">Privacy Browser Handbuch</string>
index 14913ce..f7d106f 100644 (file)
     <string name="url">URL</string>
     <string name="url_label">URL:</string>
 
-    <!-- Pinned SSL Certificate Mismatch. -->
-    <string name="update_ssl">Actualizar SSL</string>
-    <string name="ssl_certificate_mismatch">No coinciden los certificados SSL</string>
-    <string name="current_ssl">SSL actual</string>
-    <string name="pinned_ssl">SSL fijado</string>
+    <!-- Pinned Mismatch. -->
+    <string name="update">Actualizar</string>
+    <string name="current">Actual</string>
+    <string name="pinned">Fijado</string>
 
     <!-- HTTP Authentication. -->
     <string name="http_authentication">Autenticación HTTP</string>
         <item>Imágenes deshabilitadas</item>
     </string-array>
     <string name="pinned_ssl_certificate">Certificado SSL fijado</string>
-    <string name="saved_ssl_certificate">Certificado SSL guardado</string>
-    <string name="current_website_ssl_certificate">Certificado SSL actual de la web</string>
-    <string name="load_an_encrypted_website">Cargar una página web cifrada antes de abrir la configuración de dominio para rellenar el certificado SSL de la página web actual.</string>
+        <string name="saved_ssl_certificate">Certificado SSL guardado</string>
+        <string name="current_website_ssl_certificate">Certificado SSL actual de la web</string>
+        <string name="load_an_encrypted_website">Cargar una página web cifrada antes de abrir la configuración de dominio para rellenar el certificado SSL de la página web actual.</string>
 
     <!-- Import/Export. -->
     <string name="encryption">Cifrado</string>
index 2929723..db4d9d5 100644 (file)
     <string name="url">URL</string>
     <string name="url_label">URL:</string>
 
-    <!-- Pinned SSL Certificate Mismatch. -->
-    <string name="update_ssl">Aggiorna SSL</string>
-    <string name="ssl_certificate_mismatch">Incompatibilità certificato SSL</string>
-    <string name="current_ssl">SSL attuale</string>
-    <string name="pinned_ssl">SSL appuntato</string>
+    <!-- Pinned Mismatch. -->
+    <string name="update">Aggiorna</string>
+    <string name="current">Attuale</string>
+    <string name="pinned">Appuntato</string>
 
     <!-- HTTP Authentication. -->
     <string name="http_authentication">Autenticazione HTTP</string>
         <item>Immagini disabilitate</item>
     </string-array>
     <string name="pinned_ssl_certificate">Certificato SSL appuntato</string>
-    <string name="saved_ssl_certificate">Certificato SSL salvato</string>
-    <string name="current_website_ssl_certificate">Certificato SSL di questo sito</string>
-    <string name="load_an_encrypted_website">Carica un sito Web criptato prima di aprire le impostazioni dei domini per popolare il certificato SSL del sito attuale.</string>
+        <string name="saved_ssl_certificate">Certificato SSL salvato</string>
+        <string name="current_website_ssl_certificate">Certificato SSL di questo sito</string>
+        <string name="load_an_encrypted_website">Carica un sito Web criptato prima di aprire le impostazioni dei domini per popolare il certificato SSL del sito attuale.</string>
 
     <!-- Import/Export. -->
     <string name="encryption">Cifratura</string>
index 5796d02..8566521 100644 (file)
     <string name="url_label">URL:</string>
 
     <!-- Pinned SSL Certificate Mismatch. -->
-    <string name="update_ssl">Обновление SSL</string>
-    <string name="ssl_certificate_mismatch">Несоответствие сертификата SSL</string>
-    <string name="current_ssl">Текущий SSL</string>
-    <string name="pinned_ssl">Закрепленный SSL</string>
+    <string name="update">Обновление</string>
+    <string name="current">Текущий</string>
+    <string name="pinned">Закрепленный</string>
 
     <!-- HTTP Authentication. -->
     <string name="http_authentication">Аутентификация HTTP</string>
         <item>Изображения выключены</item>
     </string-array>
     <string name="pinned_ssl_certificate">Закрепленный сертификат SSL</string>
-    <string name="saved_ssl_certificate">Сохраненный сертификат SSL</string>
-    <string name="current_website_ssl_certificate">Текущий сертификат SSL сайта</string>
-    <string name="load_an_encrypted_website">Откройте зашифрованный сайт перед настройкой домена, чтобы заполнить текущий сертификат SSL веб-сайта.</string>
+        <string name="saved_ssl_certificate">Сохраненный сертификат SSL</string>
+        <string name="current_website_ssl_certificate">Текущий сертификат SSL сайта</string>
+        <string name="load_an_encrypted_website">Откройте зашифрованный сайт перед настройкой домена, чтобы заполнить текущий сертификат SSL веб-сайта.</string>
 
     <!-- Import/Export. -->
     <string name="encryption">Шифрование</string>
index 1dd52e1..0ebbe61 100644 (file)
     <string name="url">URL</string>
     <string name="url_label">URL:</string>
 
-    <!-- Pinned SSL Certificate Mismatch. -->
-    <string name="update_ssl">SSL güncelle</string>
-    <string name="ssl_certificate_mismatch">SSL Sertifikası Uyumsuzluğu</string>
-    <string name="current_ssl">Geçerli SSL</string>
-    <string name="pinned_ssl">İğneli SSL</string>
+    <!-- Pinned Mismatch. -->
+    <string name="update">Güncelle</string>
+    <string name="current">Geçerli</string>
+    <string name="pinned">İğneli</string>
 
     <!-- HTTP Authentication. -->
     <string name="http_authentication">HTTP Kimlik Doğrulama</string>
         <item>Resimler devre dışı</item>
     </string-array>
     <string name="pinned_ssl_certificate">İğneli SSL sertifikası</string>
-    <string name="saved_ssl_certificate">Kayıtlı SSL sertifikası</string>
-    <string name="current_website_ssl_certificate">Geçerli web sitesi SSL sertifikası</string>
-    <string name="load_an_encrypted_website">Geçerli web sitesinin SSL sertifikasını doldurmak için Domain Ayarlarını açmadan önce şifrelenmiş bir web sitesi yükleyin.</string>
+        <string name="saved_ssl_certificate">Kayıtlı SSL sertifikası</string>
+        <string name="current_website_ssl_certificate">Geçerli web sitesi SSL sertifikası</string>
+        <string name="load_an_encrypted_website">Geçerli web sitesinin SSL sertifikasını doldurmak için Domain Ayarlarını açmadan önce şifrelenmiş bir web sitesi yükleyin.</string>
 
     <!-- Import/Export. -->
     <string name="encryption">Şifreleme</string>
index d39bf64..7e8a037 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>
+    <!-- Pinned Mismatch. -->
+    <string name="pinned_mismatch">Pinned Mismatch</string>
+    <string name="update">Update</string>
+    <string name="current">Current</string>
+    <string name="pinned">Pinned</string>
 
     <!-- HTTP Authentication. -->
     <string name="http_authentication">HTTP Authentication</string>
         <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>
+        <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>
+    <string name="pinned_ip_addresses">Pinned IP addresses</string>
+        <string name="saved_ip_addresses">Saved IP addresses</string>
+        <string name="current_ip_addresses">Current IP addresses</string>
 
     <!-- Import/Export. -->
     <string name="encryption">Encryption</string>