2 * Copyright © 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.adapters
22 import android.content.Context
23 import android.content.res.Configuration
24 import android.net.Uri
25 import android.text.SpannableStringBuilder
26 import android.text.Spanned
27 import android.text.style.ForegroundColorSpan
28 import android.view.LayoutInflater
29 import android.view.View
30 import android.view.ViewGroup
31 import android.widget.TextView
33 import androidx.viewpager.widget.PagerAdapter
35 import com.stoutner.privacybrowser.R
36 import com.stoutner.privacybrowser.activities.MainWebViewActivity
37 import com.stoutner.privacybrowser.views.NestedScrollWebView
39 import java.text.DateFormat
42 // This adapter uses a PagerAdapter instead of a FragmentPagerAdapter because dialogs fragments really don't like having a nested FragmentPagerAdapter inside of them.
43 class PinnedMismatchPagerAdapter(private val context: Context, private val layoutInflater: LayoutInflater, private val webViewFragmentId: Long) : PagerAdapter() {
44 override fun isViewFromObject(view: View, `object`: Any): Boolean {
45 // Check to see if the view and the object are the same.
46 return view === `object`
49 // Get the number of tabs.
50 override fun getCount(): Int {
51 // There are two tabs.
55 // Get the name of each tab. Tab numbers start at 0.
56 override fun getPageTitle(tabNumber: Int): CharSequence {
57 return when (tabNumber) {
58 0 -> context.getString(R.string.current)
59 1 -> context.getString(R.string.pinned)
65 override fun instantiateItem(container: ViewGroup, tabNumber: Int): Any {
66 // Get the current position of this WebView fragment.
67 val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
69 // Get the WebView tab fragment.
70 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
72 // Get the WebView fragment view.
73 val webViewFragmentView = webViewTabFragment.requireView()
75 // Get a handle for the current WebView.
76 val nestedScrollWebView = webViewFragmentView.findViewById<NestedScrollWebView>(R.id.nestedscroll_webview)!!
78 // Inflate the scroll view for this tab.
79 val tabLayout = layoutInflater.inflate(R.layout.pinned_mismatch_tab_linearlayout, container, false) as ViewGroup
81 // Get handles for the views.
82 val domainNameTextView = tabLayout.findViewById<TextView>(R.id.domain_name)
83 val ipAddressesTextView = tabLayout.findViewById<TextView>(R.id.ip_addresses)
84 val issuedToCNameTextView = tabLayout.findViewById<TextView>(R.id.issued_to_cname)
85 val issuedToONameTextView = tabLayout.findViewById<TextView>(R.id.issued_to_oname)
86 val issuedToUNameTextView = tabLayout.findViewById<TextView>(R.id.issued_to_uname)
87 val issuedByCNameTextView = tabLayout.findViewById<TextView>(R.id.issued_by_cname)
88 val issuedByONameTextView = tabLayout.findViewById<TextView>(R.id.issued_by_oname)
89 val issuedByUNameTextView = tabLayout.findViewById<TextView>(R.id.issued_by_uname)
90 val startDateTextView = tabLayout.findViewById<TextView>(R.id.start_date)
91 val endDateTextView = tabLayout.findViewById<TextView>(R.id.end_date)
94 val domainNameLabel = context.getString(R.string.domain_label) + " "
95 val ipAddressesLabel = context.getString(R.string.ip_addresses) + " "
96 val cNameLabel = context.getString(R.string.common_name) + " "
97 val oNameLabel = context.getString(R.string.organization) + " "
98 val uNameLabel = context.getString(R.string.organizational_unit) + " "
99 val startDateLabel = context.getString(R.string.start_date) + " "
100 val endDateLabel = context.getString(R.string.end_date) + " "
102 // Convert the URL to a URI.
103 val currentUri = Uri.parse(nestedScrollWebView.url)
105 // Get the current host from the URI.
106 val domainName = currentUri.host
108 // Get the current website SSL certificate.
109 val sslCertificate = nestedScrollWebView.certificate
111 // Initialize the SSL certificate variables.
112 var currentSslIssuedToCName = ""
113 var currentSslIssuedToOName = ""
114 var currentSslIssuedToUName = ""
115 var currentSslIssuedByCName = ""
116 var currentSslIssuedByOName = ""
117 var currentSslIssuedByUName = ""
118 var currentSslStartDate: Date? = null
119 var currentSslEndDate: Date? = null
121 // Extract the individual pieces of information from the current website SSL certificate if it is not null.
122 if (sslCertificate != null) {
123 currentSslIssuedToCName = sslCertificate.issuedTo.cName
124 currentSslIssuedToOName = sslCertificate.issuedTo.oName
125 currentSslIssuedToUName = sslCertificate.issuedTo.uName
126 currentSslIssuedByCName = sslCertificate.issuedBy.cName
127 currentSslIssuedByOName = sslCertificate.issuedBy.oName
128 currentSslIssuedByUName = sslCertificate.issuedBy.uName
129 currentSslStartDate = sslCertificate.validNotBeforeDate
130 currentSslEndDate = sslCertificate.validNotAfterDate
133 // Get the pinned SSL certificate.
134 val pinnedSslCertificateArrayList = nestedScrollWebView.pinnedSslCertificate
136 // Extract the arrays from the array list.
137 val pinnedSslCertificateStringArray = pinnedSslCertificateArrayList[0] as Array<*>
138 val pinnedSslCertificateDateArray = pinnedSslCertificateArrayList[1] as Array<*>
140 // Setup the domain name spannable string builder.
141 val domainNameStringBuilder = SpannableStringBuilder(domainNameLabel + domainName)
143 // Initialize the spannable string builders.
144 val ipAddressesStringBuilder: SpannableStringBuilder
145 val issuedToCNameStringBuilder: SpannableStringBuilder
146 val issuedToONameStringBuilder: SpannableStringBuilder
147 val issuedToUNameStringBuilder: SpannableStringBuilder
148 val issuedByCNameStringBuilder: SpannableStringBuilder
149 val issuedByONameStringBuilder: SpannableStringBuilder
150 val issuedByUNameStringBuilder: SpannableStringBuilder
151 val startDateStringBuilder: SpannableStringBuilder
152 val endDateStringBuilder: SpannableStringBuilder
154 // Setup the spannable string builders for each tab.
155 if (tabNumber == 0) { // Setup the current settings tab.
156 // Create the string builders.
157 ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.currentIpAddresses)
158 issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + currentSslIssuedToCName)
159 issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + currentSslIssuedToOName)
160 issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + currentSslIssuedToUName)
161 issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + currentSslIssuedByCName)
162 issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + currentSslIssuedByOName)
163 issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + currentSslIssuedByUName)
165 // Set the dates if they aren't null. Formatting a null date causes a crash.
166 startDateStringBuilder = if (currentSslStartDate == null) {
167 SpannableStringBuilder(startDateLabel)
169 SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate))
172 endDateStringBuilder = if (currentSslEndDate == null) {
173 SpannableStringBuilder(endDateLabel)
175 SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate))
177 } else { // Setup the pinned settings tab.
178 // Create the string builders.
179 ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.pinnedIpAddresses)
180 issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + pinnedSslCertificateStringArray[0])
181 issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + pinnedSslCertificateStringArray[1])
182 issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + pinnedSslCertificateStringArray[2])
183 issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + pinnedSslCertificateStringArray[3])
184 issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + pinnedSslCertificateStringArray[4])
185 issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + pinnedSslCertificateStringArray[5])
187 // Set the dates if they aren't null. Formatting a null date causes a crash.
188 startDateStringBuilder = if (pinnedSslCertificateDateArray[0] == null) {
189 SpannableStringBuilder(startDateLabel)
191 SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(pinnedSslCertificateDateArray[0]))
194 endDateStringBuilder = if (pinnedSslCertificateDateArray[1] == null) {
195 SpannableStringBuilder(endDateLabel)
197 SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(pinnedSslCertificateDateArray[1]))
201 // Define the color spans.
202 val blueColorSpan: ForegroundColorSpan
203 val redColorSpan: ForegroundColorSpan
205 // Get the current theme status.
206 val currentThemeStatus = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
208 // Set the color spans according to the theme. The deprecated `resources` must be used until the minimum API >= 23.
209 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
210 @Suppress("DEPRECATION")
211 blueColorSpan = ForegroundColorSpan(context.resources.getColor(R.color.blue_700))
212 @Suppress("DEPRECATION")
213 redColorSpan = ForegroundColorSpan(context.resources.getColor(R.color.red_a700))
215 @Suppress("DEPRECATION")
216 blueColorSpan = ForegroundColorSpan(context.resources.getColor(R.color.violet_700))
217 @Suppress("DEPRECATION")
218 redColorSpan = ForegroundColorSpan(context.resources.getColor(R.color.red_900))
221 // Set the domain name to be blue.
222 domainNameStringBuilder.setSpan(blueColorSpan, domainNameLabel.length, domainNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
224 // Color coordinate the IP addresses if they are pinned.
225 if (nestedScrollWebView.hasPinnedIpAddresses()) {
226 if (nestedScrollWebView.currentIpAddresses == nestedScrollWebView.pinnedIpAddresses) {
227 ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
229 ipAddressesStringBuilder.setSpan(redColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
233 // Color coordinate the SSL certificate fields if they are pinned.
234 if (nestedScrollWebView.hasPinnedSslCertificate()) {
235 if (currentSslIssuedToCName == pinnedSslCertificateStringArray[0]) {
236 issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
238 issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
241 if (currentSslIssuedToOName == pinnedSslCertificateStringArray[1]) {
242 issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
244 issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
247 if (currentSslIssuedToUName == pinnedSslCertificateStringArray[2]) {
248 issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
250 issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
253 if (currentSslIssuedByCName == pinnedSslCertificateStringArray[3]) {
254 issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
256 issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
259 if (currentSslIssuedByOName == pinnedSslCertificateStringArray[4]) {
260 issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
262 issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
265 if (currentSslIssuedByUName == pinnedSslCertificateStringArray[5]) {
266 issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
268 issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
271 if (currentSslStartDate != null && currentSslStartDate == pinnedSslCertificateDateArray[0]) {
272 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
274 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
277 if (currentSslEndDate != null && currentSslEndDate == pinnedSslCertificateDateArray[1]) {
278 endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
280 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
284 // Display the strings.
285 domainNameTextView.text = domainNameStringBuilder
286 ipAddressesTextView.text = ipAddressesStringBuilder
287 issuedToCNameTextView.text = issuedToCNameStringBuilder
288 issuedToONameTextView.text = issuedToONameStringBuilder
289 issuedToUNameTextView.text = issuedToUNameStringBuilder
290 issuedByCNameTextView.text = issuedByCNameStringBuilder
291 issuedByONameTextView.text = issuedByONameStringBuilder
292 issuedByUNameTextView.text = issuedByUNameStringBuilder
293 startDateTextView.text = startDateStringBuilder
294 endDateTextView.text = endDateStringBuilder
296 // Add the tab layout to the container. This needs to be manually done for pager adapters.
297 container.addView(tabLayout)
299 // Return the tab layout.