Release 3.8.1.
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / PinnedMismatchDialog.kt
1 /*
2  * Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
22 import android.annotation.SuppressLint
23 import android.app.Dialog
24 import android.content.Context
25 import android.content.DialogInterface
26 import android.graphics.drawable.BitmapDrawable
27 import android.graphics.drawable.Drawable
28 import android.os.Bundle
29 import android.view.WindowManager
30
31 import androidx.appcompat.app.AlertDialog
32 import androidx.core.content.ContextCompat
33 import androidx.fragment.app.DialogFragment
34 import androidx.preference.PreferenceManager
35 import androidx.viewpager.widget.ViewPager
36
37 import com.google.android.material.tabs.TabLayout
38
39 import com.stoutner.privacybrowser.R
40 import com.stoutner.privacybrowser.activities.MainWebViewActivity
41 import com.stoutner.privacybrowser.adapters.PinnedMismatchPagerAdapter
42 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
43 import com.stoutner.privacybrowser.views.NestedScrollWebView
44
45 // Define the class constants.
46 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
47
48 class PinnedMismatchDialog : DialogFragment() {
49     // Declare the class variables.
50     private lateinit var pinnedMismatchListener: PinnedMismatchListener
51
52     // The public interface is used to send information back to the parent activity.
53     interface PinnedMismatchListener {
54         fun pinnedErrorGoBack()
55     }
56
57     override fun onAttach(context: Context) {
58         // Run the default commands.
59         super.onAttach(context)
60
61         // Get a handle for the listener from the launching context.
62         pinnedMismatchListener = context as PinnedMismatchListener
63     }
64
65     companion object {
66         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
67         @JvmStatic
68         fun displayDialog(webViewFragmentId: Long): PinnedMismatchDialog {
69             // Create an arguments bundle.
70             val argumentsBundle = Bundle()
71
72             // Store the WebView fragment ID in the bundle.
73             argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
74
75             // Create a new instance of the pinned mismatch dialog.
76             val pinnedMismatchDialog = PinnedMismatchDialog()
77
78             // Add the arguments bundle to the new instance.
79             pinnedMismatchDialog.arguments = argumentsBundle
80
81             // Make it so.
82             return pinnedMismatchDialog
83         }
84     }
85
86     // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
87     @SuppressLint("InflateParams")
88     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
89         // Get the WebView fragment ID.
90         val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
91
92         // Get the current position of this WebView fragment.
93         val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
94
95         // Get the WebView tab fragment.
96         val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
97
98         // Get the fragment view.
99         val fragmentView = webViewTabFragment.requireView()
100
101         // Get a handle for the current WebView.
102         val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)!!
103
104         // Use an alert dialog builder to create the alert dialog.
105         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
106
107         // Get the favorite icon.
108         val favoriteIconBitmap = nestedScrollWebView.favoriteOrDefaultIcon
109
110         // Get the default favorite icon drawable.  `ContextCompat` must be used until API >= 21.
111         val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world)
112
113         // Cast the favorite icon drawable to a bitmap drawable.
114         val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
115
116         // Store the default icon bitmap.
117         val defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.bitmap
118
119         // Set the favorite icon as the dialog icon if it exists.
120         if (favoriteIconBitmap.sameAs(defaultFavoriteIconBitmap)) {  // There is no website favorite icon.
121             // Set the icon according to the theme.
122             dialogBuilder.setIconAttribute(R.attr.sslCertificateBlueIcon)
123         } else {  // There is a favorite icon.
124             // Create a drawable version of the favorite icon.
125             val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
126
127             // Set the icon.
128             dialogBuilder.setIcon(favoriteIconDrawable)
129         }
130
131         // Set the title.
132         dialogBuilder.setTitle(R.string.pinned_mismatch)
133
134         // Set the layout.  The parent view is `null` because it will be assigned by the alert dialog.
135         dialogBuilder.setView(layoutInflater.inflate(R.layout.pinned_mismatch_linearlayout, null))
136
137         // Set the update button listener.
138         dialogBuilder.setNeutralButton(R.string.update) { _: DialogInterface?, _: Int ->
139             // Get the current SSL certificate.
140             val currentSslCertificate = nestedScrollWebView.certificate!!
141
142             // Get the dates from the certificate.
143             val currentSslStartDate = currentSslCertificate.validNotBeforeDate
144             val currentSslEndDate = currentSslCertificate.validNotAfterDate
145
146             // Convert the dates into longs.  If the date is null, a long value of `0` will be stored in the domains database entry.
147             val currentSslStartDateLong: Long = currentSslStartDate?.time ?: 0
148             val currentSslEndDateLong: Long = currentSslEndDate?.time ?: 0
149
150             // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in the domains database helper.
151             val domainsDatabaseHelper = DomainsDatabaseHelper(context, null, null, 0)
152
153             // Update the SSL certificate if it is pinned.
154             if (nestedScrollWebView.hasPinnedSslCertificate()) {
155                 // Update the pinned SSL certificate in the domain database.
156                 domainsDatabaseHelper.updatePinnedSslCertificate(nestedScrollWebView.domainSettingsDatabaseId, currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName,
157                         currentSslCertificate.issuedTo.uName, currentSslCertificate.issuedBy.cName, currentSslCertificate.issuedBy.oName, currentSslCertificate.issuedBy.uName, currentSslStartDateLong,
158                         currentSslEndDateLong)
159
160                 // Update the pinned SSL certificate in the nested scroll WebView.
161                 nestedScrollWebView.setPinnedSslCertificate(currentSslCertificate.issuedTo.cName, currentSslCertificate.issuedTo.oName, currentSslCertificate.issuedTo.uName,
162                         currentSslCertificate.issuedBy.cName, currentSslCertificate.issuedBy.oName, currentSslCertificate.issuedBy.uName, currentSslStartDate, currentSslEndDate)
163             }
164
165             // Update the IP addresses if they are pinned.
166             if (nestedScrollWebView.hasPinnedIpAddresses()) {
167                 // Update the pinned IP addresses in the domain database.
168                 domainsDatabaseHelper.updatePinnedIpAddresses(nestedScrollWebView.domainSettingsDatabaseId, nestedScrollWebView. currentIpAddresses)
169
170                 // Update the pinned IP addresses in the nested scroll WebView.
171                 nestedScrollWebView.pinnedIpAddresses = nestedScrollWebView.currentIpAddresses
172             }
173         }
174
175         // Set the back button listener.
176         dialogBuilder.setNegativeButton(R.string.back) { _: DialogInterface?, _: Int ->
177             if (nestedScrollWebView.canGoBack()) {  // There is a back page in the history.
178                 // Invoke the navigate history listener in the calling activity.  These commands cannot be run here because they need access to `applyDomainSettings()`.
179                 pinnedMismatchListener.pinnedErrorGoBack()
180             } else {  // There are no pages to go back to.
181                 // Load a blank page
182                 nestedScrollWebView.loadUrl("")
183             }
184         }
185
186         // Set the proceed button listener.
187         dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
188             // Do not check the pinned information for this domain again until the domain changes.
189             nestedScrollWebView.setIgnorePinnedDomainInformation(true)
190         }
191
192         // Create an alert dialog from the alert dialog builder.
193         val alertDialog = dialogBuilder.create()
194
195         // Get a handle for the shared preferences.
196         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
197
198         // Get the screenshot preference.
199         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
200
201         // Disable screenshots if not allowed.
202         if (!allowScreenshots) {
203             // Disable screenshots.
204             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
205         }
206
207         // The alert dialog must be shown before items in the layout can be modified.
208         alertDialog.show()
209
210         //  Get handles for the views.
211         val viewPager = alertDialog.findViewById<ViewPager>(R.id.pinned_ssl_certificate_mismatch_viewpager)!!
212         val tabLayout = alertDialog.findViewById<TabLayout>(R.id.pinned_ssl_certificate_mismatch_tablayout)!!
213
214         // Initialize the pinned mismatch pager adapter.
215         val pinnedMismatchPagerAdapter = PinnedMismatchPagerAdapter(requireContext(), layoutInflater, webViewFragmentId)
216
217         // Set the view pager adapter.
218         viewPager.adapter = pinnedMismatchPagerAdapter
219
220         // Connect the tab layout to the view pager.
221         tabLayout.setupWithViewPager(viewPager)
222
223         // Return the alert dialog.
224         return alertDialog
225     }
226 }