2 * Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
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.
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.
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/>.
20 package com.stoutner.privacybrowser.dialogs
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
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
36 import com.google.android.material.tabs.TabLayout
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
44 // Define the class constants.
45 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
47 class PinnedMismatchDialog : DialogFragment() {
48 // Declare the class variables.
49 private lateinit var pinnedMismatchListener: PinnedMismatchListener
51 // The public interface is used to send information back to the parent activity.
52 interface PinnedMismatchListener {
53 fun pinnedErrorGoBack()
56 override fun onAttach(context: Context) {
57 // Run the default commands.
58 super.onAttach(context)
60 // Get a handle for the listener from the launching context.
61 pinnedMismatchListener = context as PinnedMismatchListener
65 // `@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 fun displayDialog(webViewFragmentId: Long): PinnedMismatchDialog {
68 // Create an arguments bundle.
69 val argumentsBundle = Bundle()
71 // Store the WebView fragment ID in the bundle.
72 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
74 // Create a new instance of the pinned mismatch dialog.
75 val pinnedMismatchDialog = PinnedMismatchDialog()
77 // Add the arguments bundle to the new instance.
78 pinnedMismatchDialog.arguments = argumentsBundle
81 return pinnedMismatchDialog
85 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
86 // Get the WebView fragment ID.
87 val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
89 // Get the current position of this WebView fragment.
90 val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
92 // Get the WebView tab fragment.
93 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
95 // Get the fragment view.
96 val fragmentView = webViewTabFragment.requireView()
98 // Get a handle for the current WebView.
99 val nestedScrollWebView = fragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)!!
101 // Use an alert dialog builder to create the alert dialog.
102 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
104 // Get the favorite icon.
105 val favoriteIconBitmap = nestedScrollWebView.getFavoriteOrDefaultIcon()
107 // Get the default favorite icon drawable. `ContextCompat` must be used until API >= 21.
108 val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world)
110 // Cast the favorite icon drawable to a bitmap drawable.
111 val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
113 // Store the default icon bitmap.
114 val defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.bitmap
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 according to the theme.
119 dialogBuilder.setIconAttribute(R.attr.sslCertificateBlueIcon)
120 } else { // There is a favorite icon.
121 // Create a drawable version of the favorite icon.
122 val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
125 dialogBuilder.setIcon(favoriteIconDrawable)
129 dialogBuilder.setTitle(R.string.pinned_mismatch)
132 dialogBuilder.setView(R.layout.pinned_mismatch_linearlayout)
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!!
139 // Get the dates from the certificate.
140 val currentSslStartDate = currentSslCertificate.validNotBeforeDate
141 val currentSslEndDate = currentSslCertificate.validNotAfterDate
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
147 // 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.
148 val domainsDatabaseHelper = DomainsDatabaseHelper(context, null, null, 0)
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)
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)
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)
167 // Update the pinned IP addresses in the nested scroll WebView.
168 nestedScrollWebView.pinnedIpAddresses = nestedScrollWebView.currentIpAddresses
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.
179 nestedScrollWebView.loadUrl("")
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
189 // Create an alert dialog from the alert dialog builder.
190 val alertDialog = dialogBuilder.create()
192 // Get a handle for the shared preferences.
193 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
195 // Get the screenshot preference.
196 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
198 // Disable screenshots if not allowed.
199 if (!allowScreenshots) {
200 // Disable screenshots.
201 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
204 // The alert dialog must be shown before items in the layout can be modified.
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)!!
211 // Initialize the pinned mismatch pager adapter.
212 val pinnedMismatchPagerAdapter = PinnedMismatchPagerAdapter(requireContext(), layoutInflater, webViewFragmentId)
214 // Set the view pager adapter.
215 viewPager.adapter = pinnedMismatchPagerAdapter
217 // Connect the tab layout to the view pager.
218 tabLayout.setupWithViewPager(viewPager)
220 // Return the alert dialog.