]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedMismatchDialog.kt
Fix a crash when restarting if the pinned mismatch dialog is displayed. https:/...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / PinnedMismatchDialog.kt
1 /*
2  * Copyright 2017-2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser Android is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
22 import android.app.Dialog
23 import android.content.Context
24 import android.content.DialogInterface
25 import android.graphics.drawable.BitmapDrawable
26 import android.graphics.drawable.Drawable
27 import android.os.Bundle
28 import android.view.WindowManager
29
30 import androidx.appcompat.app.AlertDialog
31 import androidx.core.content.ContextCompat
32 import androidx.fragment.app.DialogFragment
33 import androidx.preference.PreferenceManager
34 import androidx.viewpager.widget.ViewPager
35
36 import com.google.android.material.tabs.TabLayout
37
38 import com.stoutner.privacybrowser.R
39 import com.stoutner.privacybrowser.activities.MainWebViewActivity
40 import com.stoutner.privacybrowser.adapters.PinnedMismatchPagerAdapter
41 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
42 import com.stoutner.privacybrowser.views.NestedScrollWebView
43
44 // Define the class constants.
45 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
46
47 class PinnedMismatchDialog : DialogFragment() {
48     companion object {
49         fun displayDialog(webViewFragmentId: Long): PinnedMismatchDialog {
50             // Create an arguments bundle.
51             val argumentsBundle = Bundle()
52
53             // Store the WebView fragment ID in the bundle.
54             argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
55
56             // Create a new instance of the pinned mismatch dialog.
57             val pinnedMismatchDialog = PinnedMismatchDialog()
58
59             // Add the arguments bundle to the new instance.
60             pinnedMismatchDialog.arguments = argumentsBundle
61
62             // Make it so.
63             return pinnedMismatchDialog
64         }
65     }
66
67     // Declare the class variables.
68     private lateinit var pinnedMismatchListener: PinnedMismatchListener
69
70     // The public interface is used to send information back to the parent activity.
71     interface PinnedMismatchListener {
72         fun pinnedErrorGoBack()
73     }
74
75     override fun onAttach(context: Context) {
76         // Run the default commands.
77         super.onAttach(context)
78
79         // Get a handle for the listener from the launching context.
80         pinnedMismatchListener = context as PinnedMismatchListener
81     }
82
83     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
84         // 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.
85         try {
86             // Get the WebView fragment ID.
87             val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
88
89             // Get the current position of this WebView fragment.
90             val webViewPosition = MainWebViewActivity.webViewStateAdapter!!.getPositionForId(webViewFragmentId)
91
92             // Get the WebView tab fragment.
93             val webViewTabFragment = MainWebViewActivity.webViewStateAdapter!!.getPageFragment(webViewPosition)
94
95             // Get the fragment view.
96             val fragmentView = webViewTabFragment.requireView()
97
98             // Get a handle for the current WebView.
99             val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)!!
100
101             // Use an alert dialog builder to create the alert dialog.
102             val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
103
104             // Get the favorite icon.
105             val favoriteIconBitmap = nestedScrollWebView.getFavoriteIcon()
106
107             // Get the default favorite icon drawable.  `ContextCompat` must be used until API >= 21.
108             val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world)
109
110             // Cast the favorite icon drawable to a bitmap drawable.
111             val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
112
113             // Store the default icon bitmap.
114             val defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.bitmap
115
116             // Set the favorite icon as the dialog icon if it exists.
117             if (favoriteIconBitmap.sameAs(defaultFavoriteIconBitmap)) {  // There is no website favorite icon.
118                 // Set the icon.
119                 dialogBuilder.setIcon(R.drawable.ssl_certificate)
120             } else {  // There is a favorite icon.
121                 // Create a drawable version of the favorite icon.
122                 val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
123
124                 // Set the icon.
125                 dialogBuilder.setIcon(favoriteIconDrawable)
126             }
127
128             // Set the title.
129             dialogBuilder.setTitle(R.string.pinned_mismatch)
130
131             // Set the layout.
132             dialogBuilder.setView(R.layout.pinned_mismatch_linearlayout)
133
134             // Set the update button listener.
135             dialogBuilder.setNeutralButton(R.string.update) { _: DialogInterface?, _: Int ->
136                 // Get the current SSL certificate.
137                 val currentSslCertificate = nestedScrollWebView.certificate!!
138
139                 // Get the dates from the certificate.
140                 val currentSslStartDate = currentSslCertificate.validNotBeforeDate
141                 val currentSslEndDate = currentSslCertificate.validNotAfterDate
142
143                 // Convert the dates into longs.  If the date is null, a long value of `0` will be stored in the domains database entry.
144                 val currentSslStartDateLong: Long = currentSslStartDate?.time ?: 0
145                 val currentSslEndDateLong: Long = currentSslEndDate?.time ?: 0
146
147                 // Initialize the database handler.
148                 val domainsDatabaseHelper = DomainsDatabaseHelper(requireContext())
149
150                 // Update the SSL certificate if it is pinned.
151                 if (nestedScrollWebView.hasPinnedSslCertificate()) {
152                     // Update the pinned SSL certificate in the domain database.
153                     domainsDatabaseHelper.updatePinnedSslCertificate(nestedScrollWebView.domainSettingsDatabaseId, currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName,
154                         currentSslCertificate.issuedTo.uName, currentSslCertificate.issuedBy.cName, currentSslCertificate.issuedBy.oName, currentSslCertificate.issuedBy.uName, currentSslStartDateLong,
155                         currentSslEndDateLong)
156
157                     // Update the pinned SSL certificate in the nested scroll WebView.
158                     nestedScrollWebView.setPinnedSslCertificate(currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName, currentSslCertificate.issuedTo.uName,
159                         currentSslCertificate.issuedBy.cName, currentSslCertificate.issuedBy.oName, currentSslCertificate.issuedBy.uName, currentSslStartDate, currentSslEndDate)
160                 }
161
162                 // Update the IP addresses if they are pinned.
163                 if (nestedScrollWebView.pinnedIpAddresses != "") {
164                     // Update the pinned IP addresses in the domain database.
165                     domainsDatabaseHelper.updatePinnedIpAddresses(nestedScrollWebView.domainSettingsDatabaseId, nestedScrollWebView.currentIpAddresses)
166
167                     // Update the pinned IP addresses in the nested scroll WebView.
168                     nestedScrollWebView.pinnedIpAddresses = nestedScrollWebView.currentIpAddresses
169                 }
170             }
171
172             // Set the back button listener.
173             dialogBuilder.setNegativeButton(R.string.back) { _: DialogInterface?, _: Int ->
174                 if (nestedScrollWebView.canGoBack()) {  // There is a back page in the history.
175                     // Invoke the navigate history listener in the calling activity.  These commands cannot be run here because they need access to `applyDomainSettings()`.
176                     pinnedMismatchListener.pinnedErrorGoBack()
177                 } else {  // There are no pages to go back to.
178                     // Load a blank page
179                     nestedScrollWebView.loadUrl("")
180                 }
181             }
182
183             // Set the proceed button listener.
184             dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
185                 // Do not check the pinned information for this domain again until the domain changes.
186                 nestedScrollWebView.ignorePinnedDomainInformation = true
187             }
188
189             // Create an alert dialog from the alert dialog builder.
190             val alertDialog = dialogBuilder.create()
191
192             // Get a handle for the shared preferences.
193             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
194
195             // Get the screenshot preference.
196             val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
197
198             // Disable screenshots if not allowed.
199             if (!allowScreenshots) {
200                 // Disable screenshots.
201                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
202             }
203
204             // The alert dialog must be shown before items in the layout can be modified.
205             alertDialog.show()
206
207             //  Get handles for the views.
208             val viewPager = alertDialog.findViewById<ViewPager>(R.id.pinned_ssl_certificate_mismatch_viewpager)!!
209             val tabLayout = alertDialog.findViewById<TabLayout>(R.id.pinned_ssl_certificate_mismatch_tablayout)!!
210
211             // Initialize the pinned mismatch pager adapter.
212             val pinnedMismatchPagerAdapter = PinnedMismatchPagerAdapter(requireContext(), layoutInflater, webViewFragmentId)
213
214             // Set the view pager adapter.
215             viewPager.adapter = pinnedMismatchPagerAdapter
216
217             // Connect the tab layout to the view pager.
218             tabLayout.setupWithViewPager(viewPager)
219
220             // Return the alert dialog.
221             return alertDialog
222         } catch (exception: Exception) {  // The app was restarted while the dialog was displayed.
223             // Dismiss this new instance of the dialog.  Amazingly, the old instance will be restored by Android and, even more amazingly, will be fully functional.
224             dismiss()
225
226             // Use an alert dialog builder to create an empty alert dialog.
227             val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
228
229             // Return the empty alert dialog.
230             return dialogBuilder.create()
231         }
232     }
233 }