From 3b5b81db53b0ee6f448ac3144a176c6d9042f4c8 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Wed, 17 May 2023 10:19:12 -0700 Subject: [PATCH] Fix a crash when restarting if the pinned mismatch dialog is displayed. https://redmine.stoutner.com/issues/655 --- .../dialogs/PinnedMismatchDialog.kt | 208 +++++++++--------- 1 file changed, 110 insertions(+), 98 deletions(-) diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt index 1a0ad7e3..a7ac2539 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt @@ -81,141 +81,153 @@ class PinnedMismatchDialog : DialogFragment() { } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - // Get the WebView fragment ID. - val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID) + // Try to create the dialog. This will fail if the app was restarted while the dialog is shown because the WebView view pager will not have been populated yet. + try { + // Get the WebView fragment ID. + val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID) - // Get the current position of this WebView fragment. - val webViewPosition = MainWebViewActivity.webViewStateAdapter!!.getPositionForId(webViewFragmentId) + // Get the current position of this WebView fragment. + val webViewPosition = MainWebViewActivity.webViewStateAdapter!!.getPositionForId(webViewFragmentId) - // Get the WebView tab fragment. - val webViewTabFragment = MainWebViewActivity.webViewStateAdapter!!.getPageFragment(webViewPosition) + // Get the WebView tab fragment. + val webViewTabFragment = MainWebViewActivity.webViewStateAdapter!!.getPageFragment(webViewPosition) - // Get the fragment view. - val fragmentView = webViewTabFragment.requireView() + // Get the fragment view. + val fragmentView = webViewTabFragment.requireView() - // Get a handle for the current WebView. - val nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)!! + // Get a handle for the current WebView. + val nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)!! - // Use an alert dialog builder to create the alert dialog. - val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + // Use an alert dialog builder to create the alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) - // Get the favorite icon. - val favoriteIconBitmap = nestedScrollWebView.getFavoriteIcon() + // Get the favorite icon. + val favoriteIconBitmap = nestedScrollWebView.getFavoriteIcon() - // Get the default favorite icon drawable. `ContextCompat` must be used until API >= 21. - val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world) + // Get the default favorite icon drawable. `ContextCompat` must be used until API >= 21. + val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world) - // Cast the favorite icon drawable to a bitmap drawable. - val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable) + // Cast the favorite icon drawable to a bitmap drawable. + val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable) - // Store the default icon bitmap. - val defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.bitmap + // Store the default icon bitmap. + val defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.bitmap - // Set the favorite icon as the dialog icon if it exists. - if (favoriteIconBitmap.sameAs(defaultFavoriteIconBitmap)) { // There is no website favorite icon. - // Set the icon. - dialogBuilder.setIcon(R.drawable.ssl_certificate) - } else { // There is a favorite icon. - // Create a drawable version of the favorite icon. - val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap) + // Set the favorite icon as the dialog icon if it exists. + if (favoriteIconBitmap.sameAs(defaultFavoriteIconBitmap)) { // There is no website favorite icon. + // Set the icon. + dialogBuilder.setIcon(R.drawable.ssl_certificate) + } else { // There is a favorite icon. + // Create a drawable version of the favorite icon. + val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap) - // Set the icon. - dialogBuilder.setIcon(favoriteIconDrawable) - } + // Set the icon. + dialogBuilder.setIcon(favoriteIconDrawable) + } - // Set the title. - dialogBuilder.setTitle(R.string.pinned_mismatch) + // Set the title. + dialogBuilder.setTitle(R.string.pinned_mismatch) - // Set the layout. - dialogBuilder.setView(R.layout.pinned_mismatch_linearlayout) + // Set the layout. + dialogBuilder.setView(R.layout.pinned_mismatch_linearlayout) - // Set the update button listener. - dialogBuilder.setNeutralButton(R.string.update) { _: DialogInterface?, _: Int -> - // Get the current SSL certificate. - val currentSslCertificate = nestedScrollWebView.certificate!! + // Set the update button listener. + dialogBuilder.setNeutralButton(R.string.update) { _: DialogInterface?, _: Int -> + // Get the current SSL certificate. + val currentSslCertificate = nestedScrollWebView.certificate!! - // Get the dates from the certificate. - val currentSslStartDate = currentSslCertificate.validNotBeforeDate - val currentSslEndDate = currentSslCertificate.validNotAfterDate + // Get the dates from the certificate. + val currentSslStartDate = currentSslCertificate.validNotBeforeDate + val currentSslEndDate = currentSslCertificate.validNotAfterDate - // Convert the dates into longs. If the date is null, a long value of `0` will be stored in the domains database entry. - val currentSslStartDateLong: Long = currentSslStartDate?.time ?: 0 - val currentSslEndDateLong: Long = currentSslEndDate?.time ?: 0 + // Convert the dates into longs. If the date is null, a long value of `0` will be stored in the domains database entry. + val currentSslStartDateLong: Long = currentSslStartDate?.time ?: 0 + val currentSslEndDateLong: Long = currentSslEndDate?.time ?: 0 - // Initialize the database handler. - val domainsDatabaseHelper = DomainsDatabaseHelper(requireContext()) + // Initialize the database handler. + val domainsDatabaseHelper = DomainsDatabaseHelper(requireContext()) - // Update the SSL certificate if it is pinned. - if (nestedScrollWebView.hasPinnedSslCertificate()) { - // Update the pinned SSL certificate in the domain database. - domainsDatabaseHelper.updatePinnedSslCertificate(nestedScrollWebView.domainSettingsDatabaseId, currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName, + // Update the SSL certificate if it is pinned. + if (nestedScrollWebView.hasPinnedSslCertificate()) { + // Update the pinned SSL certificate in the domain database. + domainsDatabaseHelper.updatePinnedSslCertificate(nestedScrollWebView.domainSettingsDatabaseId, currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName, currentSslCertificate.issuedTo.uName, currentSslCertificate.issuedBy.cName, currentSslCertificate.issuedBy.oName, currentSslCertificate.issuedBy.uName, currentSslStartDateLong, currentSslEndDateLong) - // Update the pinned SSL certificate in the nested scroll WebView. - nestedScrollWebView.setPinnedSslCertificate(currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName, currentSslCertificate.issuedTo.uName, + // Update the pinned SSL certificate in the nested scroll WebView. + nestedScrollWebView.setPinnedSslCertificate(currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName, currentSslCertificate.issuedTo.uName, currentSslCertificate.issuedBy.cName, currentSslCertificate.issuedBy.oName, currentSslCertificate.issuedBy.uName, currentSslStartDate, currentSslEndDate) - } + } - // Update the IP addresses if they are pinned. - if (nestedScrollWebView.pinnedIpAddresses != "") { - // Update the pinned IP addresses in the domain database. - domainsDatabaseHelper.updatePinnedIpAddresses(nestedScrollWebView.domainSettingsDatabaseId, nestedScrollWebView. currentIpAddresses) + // Update the IP addresses if they are pinned. + if (nestedScrollWebView.pinnedIpAddresses != "") { + // Update the pinned IP addresses in the domain database. + domainsDatabaseHelper.updatePinnedIpAddresses(nestedScrollWebView.domainSettingsDatabaseId, nestedScrollWebView.currentIpAddresses) - // Update the pinned IP addresses in the nested scroll WebView. - nestedScrollWebView.pinnedIpAddresses = nestedScrollWebView.currentIpAddresses + // Update the pinned IP addresses in the nested scroll WebView. + nestedScrollWebView.pinnedIpAddresses = nestedScrollWebView.currentIpAddresses + } } - } - // Set the back button listener. - dialogBuilder.setNegativeButton(R.string.back) { _: DialogInterface?, _: Int -> - if (nestedScrollWebView.canGoBack()) { // There is a back page in the history. - // Invoke the navigate history listener in the calling activity. These commands cannot be run here because they need access to `applyDomainSettings()`. - pinnedMismatchListener.pinnedErrorGoBack() - } else { // There are no pages to go back to. - // Load a blank page - nestedScrollWebView.loadUrl("") + // Set the back button listener. + dialogBuilder.setNegativeButton(R.string.back) { _: DialogInterface?, _: Int -> + if (nestedScrollWebView.canGoBack()) { // There is a back page in the history. + // Invoke the navigate history listener in the calling activity. These commands cannot be run here because they need access to `applyDomainSettings()`. + pinnedMismatchListener.pinnedErrorGoBack() + } else { // There are no pages to go back to. + // Load a blank page + nestedScrollWebView.loadUrl("") + } } - } - // Set the proceed button listener. - dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int -> - // Do not check the pinned information for this domain again until the domain changes. - nestedScrollWebView.ignorePinnedDomainInformation = true - } + // Set the proceed button listener. + dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int -> + // Do not check the pinned information for this domain again until the domain changes. + nestedScrollWebView.ignorePinnedDomainInformation = true + } - // Create an alert dialog from the alert dialog builder. - val alertDialog = dialogBuilder.create() + // Create an alert dialog from the alert dialog builder. + val alertDialog = dialogBuilder.create() - // Get a handle for the shared preferences. - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) - // Get the screenshot preference. - val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + // Get the screenshot preference. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) - // Disable screenshots if not allowed. - if (!allowScreenshots) { - // Disable screenshots. - alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Disable screenshots. + alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } - // The alert dialog must be shown before items in the layout can be modified. - alertDialog.show() + // The alert dialog must be shown before items in the layout can be modified. + alertDialog.show() - // Get handles for the views. - val viewPager = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager)!! - val tabLayout = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_tablayout)!! + // Get handles for the views. + val viewPager = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager)!! + val tabLayout = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_tablayout)!! - // Initialize the pinned mismatch pager adapter. - val pinnedMismatchPagerAdapter = PinnedMismatchPagerAdapter(requireContext(), layoutInflater, webViewFragmentId) + // Initialize the pinned mismatch pager adapter. + val pinnedMismatchPagerAdapter = PinnedMismatchPagerAdapter(requireContext(), layoutInflater, webViewFragmentId) - // Set the view pager adapter. - viewPager.adapter = pinnedMismatchPagerAdapter + // Set the view pager adapter. + viewPager.adapter = pinnedMismatchPagerAdapter - // Connect the tab layout to the view pager. - tabLayout.setupWithViewPager(viewPager) + // Connect the tab layout to the view pager. + tabLayout.setupWithViewPager(viewPager) - // Return the alert dialog. - return alertDialog + // Return the alert dialog. + return alertDialog + } catch (exception: Exception) { // The app was restarted while the dialog was displayed. + // Dismiss this new instance of the dialog. Amazingly, the old instance will be restored by Android and, even more amazingly, will be fully functional. + dismiss() + + // Use an alert dialog builder to create an empty alert dialog. + val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) + + // Return the empty alert dialog. + return dialogBuilder.create() + } } } -- 2.43.0