From f1c84b89fdef2294e9fadb67e2baec937eae6ed8 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Sat, 17 Apr 2021 15:48:59 -0700 Subject: [PATCH] Fix a crash if the View SSL Certificate is displayed when the app restarts. https://redmine.stoutner.com/issues/694 --- .../dialogs/CreateHomeScreenShortcutDialog.kt | 12 +- .../dialogs/SslCertificateErrorDialog.kt | 1 - .../dialogs/ViewSslCertificateDialog.kt | 214 +++++++++++++----- 3 files changed, 169 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt index 32c4e51a..3e405c8d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.kt @@ -71,7 +71,7 @@ class CreateHomeScreenShortcutDialog : DialogFragment() { // Convert the favorite icon to a PNG and place it in the byte array output stream. `0` is for lossless compression (the only option for a PNG). favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream) - // Convert the byte array output stream to a byte array. + // Convert the favorite icon byte array output stream to a byte array. val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray() // Create an arguments bundle. @@ -107,14 +107,16 @@ class CreateHomeScreenShortcutDialog : DialogFragment() { // Convert the favorite icon byte array to a bitmap. val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size) - // Use an alert dialog builder to create the dialog. - val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) - // Create a drawable version of the favorite icon. val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap) - // Set the title and icon. + // Use an alert dialog builder to create the dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + + // Set the title. dialogBuilder.setTitle(R.string.create_shortcut) + + // Set the icon. dialogBuilder.setIcon(favoriteIconDrawable) // Set the view. The parent view is null because it will be assigned by the alert dialog. diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt index 139e6576..a4082552 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt @@ -336,7 +336,6 @@ class SslCertificateErrorDialog : DialogFragment() { primaryErrorString = getString(R.string.invalid_certificate) } - // Display the strings. primaryErrorTextView.text = primaryErrorString urlTextView.text = urlStringBuilder diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt index 2bebf905..3c971dc6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt @@ -22,6 +22,8 @@ package com.stoutner.privacybrowser.dialogs import android.annotation.SuppressLint import android.app.Dialog import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.net.Uri @@ -39,14 +41,47 @@ import androidx.preference.PreferenceManager import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.activities.MainWebViewActivity import com.stoutner.privacybrowser.views.NestedScrollWebView +import java.io.ByteArrayOutputStream import java.text.DateFormat import java.util.Calendar +import java.util.Date // Define the class constants. private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id" +private const val HAS_SSL_CERTIFICATE = "has_ssl_certificate" +private const val FAVORITE_ICON = "favorite_icon" +private const val DOMAIN = "domain" +private const val IP_ADDRESSES = "ip_addresses" +private const val ISSUED_TO_CNAME = "issued_to_cname" +private const val ISSUED_TO_ONAME = "issued_to_oname" +private const val ISSUED_TO_UNAME = "issued_to_uname" +private const val ISSUED_BY_CNAME = "issued_by_cname" +private const val ISSUED_BY_ONAME = "issued_by_oname" +private const val ISSUED_BY_UNAME = "issued_by_uname" +private const val START_DATE = "start_date" +private const val END_DATE = "end_date" class ViewSslCertificateDialog : DialogFragment() { + // Define the class variables. + private var hasSslCertificate: Boolean = false + + // Declare the class variables. + private lateinit var favoriteIconDrawable: Drawable + private lateinit var domainString: String + private lateinit var ipAddresses: String + private lateinit var issuedToCName: String + private lateinit var issuedToOName: String + private lateinit var issuedToUName: String + private lateinit var issuedByCName: String + private lateinit var issuedByOName: String + private lateinit var issuedByUName: String + private lateinit var startDate: Date + private lateinit var endDate: Date + + // Declare the class views. + private lateinit var nestedScrollWebView: NestedScrollWebView + companion object { // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. @JvmStatic @@ -71,23 +106,79 @@ class ViewSslCertificateDialog : DialogFragment() { // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog. @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - // Get the current position of this WebView fragment. - val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(requireArguments().getLong(WEBVIEW_FRAGMENT_ID)) + // Use a builder to create the alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) - // Get the WebView tab fragment. - val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition) + // Populate the class variables. + if (savedInstanceState == null) { // The dialog is starting for the first time. + // Get the current position of this WebView fragment. + val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(requireArguments().getLong(WEBVIEW_FRAGMENT_ID)) - // Get the fragment view. - val fragmentView = webViewTabFragment.requireView() + // Get the WebView tab fragment. + val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition) - // Get a handle for the current nested scroll WebView. - val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview) + // Get the fragment view. + val fragmentView = webViewTabFragment.requireView() - // Use a builder to create the alert dialog. - val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + // Get a handle for the current nested scroll WebView. + nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview) + + // Get the SSL certificate. + val sslCertificate = nestedScrollWebView.certificate + + // Store the status of the SSL certificate. + hasSslCertificate = sslCertificate != null - // Create a drawable version of the favorite icon. - val favoriteIconDrawable: Drawable = BitmapDrawable(resources, nestedScrollWebView.favoriteOrDefaultIcon) + // Create a drawable version of the favorite icon. + favoriteIconDrawable = BitmapDrawable(resources, nestedScrollWebView.favoriteOrDefaultIcon) + + // Populate the certificate class variables if the webpage has an SSL certificate. + if (hasSslCertificate) { + // Convert the URL to a URI. + val uri = Uri.parse(nestedScrollWebView.url) + + // Extract the domain name from the URI. + domainString = uri.host!! + + // Get the ip addresses from the nested scroll WebView. + ipAddresses = nestedScrollWebView.currentIpAddresses + + // Get the strings from the SSL certificate. + issuedToCName = sslCertificate!!.issuedTo.cName + issuedToOName = sslCertificate.issuedTo.oName + issuedToUName = sslCertificate.issuedTo.uName + issuedByCName = sslCertificate.issuedBy.cName + issuedByOName = sslCertificate.issuedBy.oName + issuedByUName = sslCertificate.issuedBy.uName + startDate = sslCertificate.validNotBeforeDate + endDate = sslCertificate.validNotAfterDate + } + } else { // The dialog has been restarted. + // Get the data from the saved instance state. + hasSslCertificate = savedInstanceState.getBoolean(HAS_SSL_CERTIFICATE) + val favoriteIconByteArray = savedInstanceState.getByteArray(FAVORITE_ICON)!! + + // Convert the favorite icon byte array to a bitmap. + val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size) + + // Create a drawable version of the favorite icon. + favoriteIconDrawable = BitmapDrawable(resources, favoriteIconBitmap) + + // Populate the certificate class variables if the webpage has an SSL certificate. + if (hasSslCertificate) { + // Populate the certificate class variables from the saved instance state. + domainString = savedInstanceState.getString(DOMAIN)!! + ipAddresses = savedInstanceState.getString(IP_ADDRESSES)!! + issuedToCName = savedInstanceState.getString(ISSUED_TO_CNAME)!! + issuedToOName = savedInstanceState.getString(ISSUED_TO_ONAME)!! + issuedToUName = savedInstanceState.getString(ISSUED_TO_UNAME)!! + issuedByCName = savedInstanceState.getString(ISSUED_BY_CNAME)!! + issuedByOName = savedInstanceState.getString(ISSUED_BY_ONAME)!! + issuedByUName = savedInstanceState.getString(ISSUED_BY_UNAME)!! + startDate = Date(savedInstanceState.getLong(START_DATE)) + endDate = Date(savedInstanceState.getLong(END_DATE)) + } + } // Set the icon. dialogBuilder.setIcon(favoriteIconDrawable) @@ -95,9 +186,6 @@ class ViewSslCertificateDialog : DialogFragment() { // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.close, null) - // Get the SSL certificate. - val sslCertificate = nestedScrollWebView.certificate - // Get a handle for the shared preferences. val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) @@ -105,25 +193,7 @@ class ViewSslCertificateDialog : DialogFragment() { val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) // Check to see if the website is encrypted. - if (sslCertificate == null) { // The website is not encrypted. - // Set the title. - dialogBuilder.setTitle(R.string.unencrypted_website) - - // Set the Layout. The parent view is `null` because it will be assigned by the alert dialog. - dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website_dialog, null)) - - // Create an alert dialog from the builder. - val alertDialog = dialogBuilder.create() - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - // Disable screenshots. - alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - - // Return the alert dialog. - return alertDialog - } else { // The website is encrypted. + if (hasSslCertificate) { // The website is encrypted. // Set the title. dialogBuilder.setTitle(R.string.ssl_certificate) @@ -163,25 +233,9 @@ class ViewSslCertificateDialog : DialogFragment() { val startDateLabel = getString(R.string.start_date) + " " val endDateLabel = getString(R.string.end_date) + " " - // Convert the URL to a URI. - val uri = Uri.parse(nestedScrollWebView.url) - - // Extract the domain name from the URI. - val domainString = uri.host - - // Get the strings from the SSL certificate. - val issuedToCName = sslCertificate.issuedTo.cName - val issuedToOName = sslCertificate.issuedTo.oName - val issuedToUName = sslCertificate.issuedTo.uName - val issuedByCName = sslCertificate.issuedBy.cName - val issuedByOName = sslCertificate.issuedBy.oName - val issuedByUName = sslCertificate.issuedBy.uName - val startDate = sslCertificate.validNotBeforeDate - val endDate = sslCertificate.validNotAfterDate - // Create spannable string builders for each text view that needs multiple colors of text. val domainStringBuilder = SpannableStringBuilder(domainLabel + domainString) - val ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.currentIpAddresses) + val ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + ipAddresses) val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName) val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName) val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName) @@ -221,7 +275,7 @@ class ViewSslCertificateDialog : DialogFragment() { val baseCertificateDomain = issuedToCName.substring(2) // Setup a copy of the domain string to test subdomains. - var domainStringSubdomain = domainString!! + var domainStringSubdomain = domainString // Define a domain names match variable. var domainNamesMatch = false @@ -292,6 +346,62 @@ class ViewSslCertificateDialog : DialogFragment() { // Return the alert dialog. return alertDialog + } else { // The website is not encrypted. + // Set the title. + dialogBuilder.setTitle(R.string.unencrypted_website) + + // Set the Layout. The parent view is `null` because it will be assigned by the alert dialog. + dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website_dialog, null)) + + // Create an alert dialog from the builder. + val alertDialog = dialogBuilder.create() + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Disable screenshots. + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // Return the alert dialog. + return alertDialog + } + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + // Run the default commands. + super.onSaveInstanceState(savedInstanceState) + + // Get the favorite icon bitmap drawable. + val favoriteIconBitmapDrawable = favoriteIconDrawable as BitmapDrawable + + // Get the favorite icon bitmap. + val favoriteIconBitmap = favoriteIconBitmapDrawable.bitmap + + // Create a favorite icon byte array output stream. + val favoriteIconByteArrayOutputStream = ByteArrayOutputStream() + + // Convert the bitmap to a PNG and place it in the byte array output stream. `0` is for lossless compression (the only option for a PNG). + favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream) + + // Convert the favorite icon byte array output stream to a byte array. + val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray() + + // Save the common class variables. + savedInstanceState.putBoolean(HAS_SSL_CERTIFICATE, hasSslCertificate) + savedInstanceState.putByteArray(FAVORITE_ICON, favoriteIconByteArray) + + // Save the SSL certificate strings if they exist. + if (hasSslCertificate) { + savedInstanceState.putString(DOMAIN, domainString) + savedInstanceState.putString(IP_ADDRESSES, ipAddresses) + savedInstanceState.putString(ISSUED_TO_CNAME, issuedToCName) + savedInstanceState.putString(ISSUED_TO_ONAME, issuedToOName) + savedInstanceState.putString(ISSUED_TO_UNAME, issuedToUName) + savedInstanceState.putString(ISSUED_BY_CNAME, issuedByCName) + savedInstanceState.putString(ISSUED_BY_ONAME, issuedByOName) + savedInstanceState.putString(ISSUED_BY_UNAME, issuedByUName) + savedInstanceState.putLong(START_DATE, startDate.time) + savedInstanceState.putLong(END_DATE, endDate.time) } } } \ No newline at end of file -- 2.45.2