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