2 * Copyright © 2021-2022 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
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.
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.
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/>.
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.getPinnedSslCertificate()
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 blueColorSpan = ForegroundColorSpan(context.getColor(R.color.blue_700))
211 redColorSpan = ForegroundColorSpan(context.getColor(R.color.red_a700))
213 blueColorSpan = ForegroundColorSpan(context.getColor(R.color.violet_700))
214 redColorSpan = ForegroundColorSpan(context.getColor(R.color.red_900))
217 // Set the domain name to be blue.
218 domainNameStringBuilder.setSpan(blueColorSpan, domainNameLabel.length, domainNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
220 // Color coordinate the IP addresses if they are pinned.
221 if (nestedScrollWebView.pinnedIpAddresses != "") {
222 if (nestedScrollWebView.currentIpAddresses == nestedScrollWebView.pinnedIpAddresses) {
223 ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
225 ipAddressesStringBuilder.setSpan(redColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
229 // Color coordinate the SSL certificate fields if they are pinned.
230 if (nestedScrollWebView.hasPinnedSslCertificate()) {
231 if (currentSslIssuedToCName == pinnedSslCertificateStringArray[0]) {
232 issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
234 issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
237 if (currentSslIssuedToOName == pinnedSslCertificateStringArray[1]) {
238 issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
240 issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
243 if (currentSslIssuedToUName == pinnedSslCertificateStringArray[2]) {
244 issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
246 issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
249 if (currentSslIssuedByCName == pinnedSslCertificateStringArray[3]) {
250 issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
252 issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
255 if (currentSslIssuedByOName == pinnedSslCertificateStringArray[4]) {
256 issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
258 issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
261 if (currentSslIssuedByUName == pinnedSslCertificateStringArray[5]) {
262 issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
264 issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
267 if (currentSslStartDate != null && currentSslStartDate == pinnedSslCertificateDateArray[0]) {
268 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
270 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
273 if (currentSslEndDate != null && currentSslEndDate == pinnedSslCertificateDateArray[1]) {
274 endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
276 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
280 // Display the strings.
281 domainNameTextView.text = domainNameStringBuilder
282 ipAddressesTextView.text = ipAddressesStringBuilder
283 issuedToCNameTextView.text = issuedToCNameStringBuilder
284 issuedToONameTextView.text = issuedToONameStringBuilder
285 issuedToUNameTextView.text = issuedToUNameStringBuilder
286 issuedByCNameTextView.text = issuedByCNameStringBuilder
287 issuedByONameTextView.text = issuedByONameStringBuilder
288 issuedByUNameTextView.text = issuedByUNameStringBuilder
289 startDateTextView.text = startDateStringBuilder
290 endDateTextView.text = endDateStringBuilder
292 // Add the tab layout to the container. This needs to be manually done for pager adapters.
293 container.addView(tabLayout)
295 // Return the tab layout.