]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.kt
Fix WebView's dark mode on API 29-32. https://redmine.stoutner.com/issues/927
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / fragments / DomainSettingsFragment.kt
1 /*
2  * Copyright 2017-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.fragments
21
22 import android.annotation.SuppressLint
23 import android.content.res.Configuration
24 import android.os.Build
25 import android.os.Bundle
26 import android.text.Editable
27 import android.text.SpannableStringBuilder
28 import android.text.Spanned
29 import android.text.TextWatcher
30 import android.text.style.ForegroundColorSpan
31 import android.view.LayoutInflater
32 import android.view.View
33 import android.view.ViewGroup
34 import android.webkit.WebView
35 import android.widget.AdapterView
36 import android.widget.ArrayAdapter
37 import android.widget.CompoundButton
38 import android.widget.EditText
39 import android.widget.ImageView
40 import android.widget.LinearLayout
41 import android.widget.RadioButton
42 import android.widget.ScrollView
43 import android.widget.Spinner
44 import android.widget.TextView
45
46 import androidx.appcompat.widget.SwitchCompat
47 import androidx.cardview.widget.CardView
48 import androidx.core.content.res.ResourcesCompat
49 import androidx.fragment.app.Fragment
50 import androidx.preference.PreferenceManager
51
52 import com.stoutner.privacybrowser.R
53 import com.stoutner.privacybrowser.activities.DomainsActivity
54 import com.stoutner.privacybrowser.activities.MainWebViewActivity
55 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
56
57 import java.lang.IndexOutOfBoundsException
58 import java.text.DateFormat
59 import java.util.Calendar
60 import java.util.Date
61
62 class DomainSettingsFragment : Fragment() {
63     // Define the class variables.
64     private var scrollY = 0
65
66     companion object {
67         // Define the public constants.
68         const val DATABASE_ID = "database_id"
69         const val SCROLL_Y = "scroll_y"
70
71         // Define the public variables.  `databaseId` is public so it can be accessed from `DomainsActivity`.
72         var databaseId = 0
73     }
74
75     override fun onCreate(savedInstanceState: Bundle?) {
76         // Run the default commands.
77         super.onCreate(savedInstanceState)
78
79         // Store the arguments in class variables.
80         databaseId = requireArguments().getInt(DATABASE_ID)
81         scrollY = requireArguments().getInt(SCROLL_Y)
82     }
83
84     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
85         // Inflate the layout.  The fragment will take care of attaching the root automatically.
86         val domainSettingsView = inflater.inflate(R.layout.domain_settings_fragment, container, false)
87
88         // Get the current theme status.
89         val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
90
91         // Get a handle for the shared preference.
92         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
93
94         // Store the default settings.
95         val defaultUserAgentName = sharedPreferences.getString(getString(R.string.user_agent_key), getString(R.string.user_agent_default_value))
96         val defaultCustomUserAgentString = sharedPreferences.getString(getString(R.string.custom_user_agent_key), getString(R.string.custom_user_agent_default_value))
97         val defaultFontSizeString = sharedPreferences.getString(getString(R.string.font_size_key), getString(R.string.font_size_default_value))
98         val defaultSwipeToRefresh = sharedPreferences.getBoolean(getString(R.string.swipe_to_refresh_key), true)
99         val defaultWebViewTheme = sharedPreferences.getString(getString(R.string.webview_theme_key), getString(R.string.webview_theme_default_value))
100         val defaultWideViewport = sharedPreferences.getBoolean(getString(R.string.wide_viewport_key), true)
101         val defaultDisplayWebpageImages = sharedPreferences.getBoolean(getString(R.string.display_webpage_images_key), true)
102
103         // Get handles for the views.
104         val domainSettingsScrollView = domainSettingsView.findViewById<ScrollView>(R.id.domain_settings_scrollview)
105         val domainNameEditText = domainSettingsView.findViewById<EditText>(R.id.domain_settings_name_edittext)
106         val javaScriptImageView = domainSettingsView.findViewById<ImageView>(R.id.javascript_imageview)
107         val javaScriptSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.javascript_switch)
108         val cookiesImageView = domainSettingsView.findViewById<ImageView>(R.id.cookies_imageview)
109         val cookiesSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.cookies_switch)
110         val domStorageImageView = domainSettingsView.findViewById<ImageView>(R.id.dom_storage_imageview)
111         val domStorageSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.dom_storage_switch)
112         val formDataImageView = domainSettingsView.findViewById<ImageView>(R.id.form_data_imageview) // The form data views can be remove once the minimum API >= 26.
113         val formDataSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.form_data_switch) // The form data views can be remove once the minimum API >= 26.
114         val easyListImageView = domainSettingsView.findViewById<ImageView>(R.id.easylist_imageview)
115         val easyListSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.easylist_switch)
116         val easyPrivacyImageView = domainSettingsView.findViewById<ImageView>(R.id.easyprivacy_imageview)
117         val easyPrivacySwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.easyprivacy_switch)
118         val fanboysAnnoyanceListImageView = domainSettingsView.findViewById<ImageView>(R.id.fanboys_annoyance_list_imageview)
119         val fanboysAnnoyanceListSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.fanboys_annoyance_list_switch)
120         val fanboysSocialBlockingListImageView = domainSettingsView.findViewById<ImageView>(R.id.fanboys_social_blocking_list_imageview)
121         val fanboysSocialBlockingListSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.fanboys_social_blocking_list_switch)
122         val ultraListImageView = domainSettingsView.findViewById<ImageView>(R.id.ultralist_imageview)
123         val ultraListSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.ultralist_switch)
124         val ultraPrivacyImageView = domainSettingsView.findViewById<ImageView>(R.id.ultraprivacy_imageview)
125         val ultraPrivacySwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.ultraprivacy_switch)
126         val blockAllThirdPartyRequestsImageView = domainSettingsView.findViewById<ImageView>(R.id.block_all_third_party_requests_imageview)
127         val blockAllThirdPartyRequestsSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.block_all_third_party_requests_switch)
128         val userAgentSpinner = domainSettingsView.findViewById<Spinner>(R.id.user_agent_spinner)
129         val userAgentTextView = domainSettingsView.findViewById<TextView>(R.id.user_agent_textview)
130         val customUserAgentEditText = domainSettingsView.findViewById<EditText>(R.id.custom_user_agent_edittext)
131         val fontSizeSpinner = domainSettingsView.findViewById<Spinner>(R.id.font_size_spinner)
132         val defaultFontSizeTextView = domainSettingsView.findViewById<TextView>(R.id.default_font_size_textview)
133         val customFontSizeEditText = domainSettingsView.findViewById<EditText>(R.id.custom_font_size_edittext)
134         val swipeToRefreshImageView = domainSettingsView.findViewById<ImageView>(R.id.swipe_to_refresh_imageview)
135         val swipeToRefreshSpinner = domainSettingsView.findViewById<Spinner>(R.id.swipe_to_refresh_spinner)
136         val swipeToRefreshTextView = domainSettingsView.findViewById<TextView>(R.id.swipe_to_refresh_textview)
137         val webViewThemeLinearLayout = domainSettingsView.findViewById<LinearLayout>(R.id.webview_theme_linearlayout)
138         val webViewThemeImageView = domainSettingsView.findViewById<ImageView>(R.id.webview_theme_imageview)
139         val webViewThemeSpinner = domainSettingsView.findViewById<Spinner>(R.id.webview_theme_spinner)
140         val webViewThemeTextView = domainSettingsView.findViewById<TextView>(R.id.webview_theme_textview)
141         val wideViewportImageView = domainSettingsView.findViewById<ImageView>(R.id.wide_viewport_imageview)
142         val wideViewportSpinner = domainSettingsView.findViewById<Spinner>(R.id.wide_viewport_spinner)
143         val wideViewportTextView = domainSettingsView.findViewById<TextView>(R.id.wide_viewport_textview)
144         val displayWebpageImagesImageView = domainSettingsView.findViewById<ImageView>(R.id.display_webpage_images_imageview)
145         val displayWebpageImagesSpinner = domainSettingsView.findViewById<Spinner>(R.id.display_webpage_images_spinner)
146         val displayImagesTextView = domainSettingsView.findViewById<TextView>(R.id.display_webpage_images_textview)
147         val pinnedSslCertificateImageView = domainSettingsView.findViewById<ImageView>(R.id.pinned_ssl_certificate_imageview)
148         val pinnedSslCertificateSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.pinned_ssl_certificate_switch)
149         val savedSslCardView = domainSettingsView.findViewById<CardView>(R.id.saved_ssl_certificate_cardview)
150         val savedSslCertificateLinearLayout = domainSettingsView.findViewById<LinearLayout>(R.id.saved_ssl_certificate_linearlayout)
151         val savedSslCertificateRadioButton = domainSettingsView.findViewById<RadioButton>(R.id.saved_ssl_certificate_radiobutton)
152         val savedSslIssuedToCNameTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ssl_certificate_issued_to_cname)
153         val savedSslIssuedToONameTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ssl_certificate_issued_to_oname)
154         val savedSslIssuedToUNameTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ssl_certificate_issued_to_uname)
155         val savedSslIssuedByCNameTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ssl_certificate_issued_by_cname)
156         val savedSslIssuedByONameTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ssl_certificate_issued_by_oname)
157         val savedSslIssuedByUNameTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ssl_certificate_issued_by_uname)
158         val savedSslStartDateTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ssl_certificate_start_date)
159         val savedSslEndDateTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ssl_certificate_end_date)
160         val currentSslCardView = domainSettingsView.findViewById<CardView>(R.id.current_website_certificate_cardview)
161         val currentWebsiteCertificateLinearLayout = domainSettingsView.findViewById<LinearLayout>(R.id.current_website_certificate_linearlayout)
162         val currentWebsiteCertificateRadioButton = domainSettingsView.findViewById<RadioButton>(R.id.current_website_certificate_radiobutton)
163         val currentSslIssuedToCNameTextView = domainSettingsView.findViewById<TextView>(R.id.current_website_certificate_issued_to_cname)
164         val currentSslIssuedToONameTextView = domainSettingsView.findViewById<TextView>(R.id.current_website_certificate_issued_to_oname)
165         val currentSslIssuedToUNameTextView = domainSettingsView.findViewById<TextView>(R.id.current_website_certificate_issued_to_uname)
166         val currentSslIssuedByCNameTextView = domainSettingsView.findViewById<TextView>(R.id.current_website_certificate_issued_by_cname)
167         val currentSslIssuedByONameTextView = domainSettingsView.findViewById<TextView>(R.id.current_website_certificate_issued_by_oname)
168         val currentSslIssuedByUNameTextView = domainSettingsView.findViewById<TextView>(R.id.current_website_certificate_issued_by_uname)
169         val currentSslStartDateTextView = domainSettingsView.findViewById<TextView>(R.id.current_website_certificate_start_date)
170         val currentSslEndDateTextView = domainSettingsView.findViewById<TextView>(R.id.current_website_certificate_end_date)
171         val noCurrentWebsiteCertificateTextView = domainSettingsView.findViewById<TextView>(R.id.no_current_website_certificate)
172         val pinnedIpAddressesImageView = domainSettingsView.findViewById<ImageView>(R.id.pinned_ip_addresses_imageview)
173         val pinnedIpAddressesSwitch = domainSettingsView.findViewById<SwitchCompat>(R.id.pinned_ip_addresses_switch)
174         val savedIpAddressesCardView = domainSettingsView.findViewById<CardView>(R.id.saved_ip_addresses_cardview)
175         val savedIpAddressesLinearLayout = domainSettingsView.findViewById<LinearLayout>(R.id.saved_ip_addresses_linearlayout)
176         val savedIpAddressesRadioButton = domainSettingsView.findViewById<RadioButton>(R.id.saved_ip_addresses_radiobutton)
177         val savedIpAddressesTextView = domainSettingsView.findViewById<TextView>(R.id.saved_ip_addresses_textview)
178         val currentIpAddressesCardView = domainSettingsView.findViewById<CardView>(R.id.current_ip_addresses_cardview)
179         val currentIpAddressesLinearLayout = domainSettingsView.findViewById<LinearLayout>(R.id.current_ip_addresses_linearlayout)
180         val currentIpAddressesRadioButton = domainSettingsView.findViewById<RadioButton>(R.id.current_ip_addresses_radiobutton)
181         val currentIpAddressesTextView = domainSettingsView.findViewById<TextView>(R.id.current_ip_addresses_textview)
182
183         // Hide the WebView theme linear layout if the API < 29.
184         if (Build.VERSION.SDK_INT < 29)
185             webViewThemeLinearLayout.visibility = View.GONE
186
187         // Setup the pinned labels.
188         val cNameLabel = getString(R.string.common_name) + "  "
189         val oNameLabel = getString(R.string.organization) + "  "
190         val uNameLabel = getString(R.string.organizational_unit) + "  "
191         val startDateLabel = getString(R.string.start_date) + "  "
192         val endDateLabel = getString(R.string.end_date) + "  "
193
194         // Initialize the database handler.
195         val domainsDatabaseHelper = DomainsDatabaseHelper(requireContext())
196
197         // Get the database cursor for this ID.
198         val domainCursor = domainsDatabaseHelper.getCursorForId(databaseId)
199
200         // Move to the first row.
201         domainCursor.moveToFirst()
202
203         // Save the cursor entries as variables.
204         val domainNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DOMAIN_NAME))
205         val javaScriptInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_JAVASCRIPT))
206         val cookiesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.COOKIES))
207         val domStorageInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_DOM_STORAGE))
208         val formDataInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FORM_DATA)) // Form data can be remove once the minimum API >= 26.
209         val easyListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYLIST))
210         val easyPrivacyInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_EASYPRIVACY))
211         val fanboysAnnoyanceListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST))
212         val fanboysSocialBlockingListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST))
213         val ultraListInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ULTRALIST))
214         val ultraPrivacyInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY))
215         val blockAllThirdPartyRequestsInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS))
216         val currentUserAgentName = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.USER_AGENT))
217         val fontSizeInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.FONT_SIZE))
218         val swipeToRefreshInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SWIPE_TO_REFRESH))
219         val webViewThemeInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WEBVIEW_THEME))
220         val wideViewportInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.WIDE_VIEWPORT))
221         val displayImagesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.DISPLAY_IMAGES))
222         val pinnedSslCertificateInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE))
223         val savedSslIssuedToCNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME))
224         val savedSslIssuedToONameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION))
225         val savedSslIssuedToUNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT))
226         val savedSslIssuedByCNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME))
227         val savedSslIssuedByONameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION))
228         val savedSslIssuedByUNameString = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT))
229         val pinnedIpAddressesInt = domainCursor.getInt(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.PINNED_IP_ADDRESSES))
230         val savedIpAddresses = domainCursor.getString(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.IP_ADDRESSES))
231
232         // Get the SSL dates from the database.
233         val savedSslStartDateLong = domainCursor.getLong(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_START_DATE))
234         val savedSslEndDateLong = domainCursor.getLong(domainCursor.getColumnIndexOrThrow(DomainsDatabaseHelper.SSL_END_DATE))
235
236         // Initialize the saved SSL certificate date variables.
237         var savedSslStartDate: Date? = null
238         var savedSslEndDate: Date? = null
239
240         // Only get the saved SSL certificate dates from the cursor if they are not set to `0`.
241         if (savedSslStartDateLong != 0L)
242             savedSslStartDate = Date(savedSslStartDateLong)
243         if (savedSslEndDateLong != 0L)
244             savedSslEndDate = Date(savedSslEndDateLong)
245
246         // Create array adapters for the spinners.
247         val translatedUserAgentArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.translated_domain_settings_user_agent_names, R.layout.spinner_item)
248         val fontSizeArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.font_size_array, R.layout.spinner_item)
249         val swipeToRefreshArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.swipe_to_refresh_array, R.layout.spinner_item)
250         val webViewThemeArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.webview_theme_array, R.layout.spinner_item)
251         val wideViewportArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.wide_viewport_array, R.layout.spinner_item)
252         val displayImagesArrayAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.display_webpage_images_array, R.layout.spinner_item)
253
254         // Set the drop down view resource on the spinners.
255         translatedUserAgentArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items)
256         fontSizeArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items)
257         swipeToRefreshArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items)
258         webViewThemeArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items)
259         wideViewportArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items)
260         displayImagesArrayAdapter.setDropDownViewResource(R.layout.domain_settings_spinner_dropdown_items)
261
262         // Set the array adapters for the spinners.
263         userAgentSpinner.adapter = translatedUserAgentArrayAdapter
264         fontSizeSpinner.adapter = fontSizeArrayAdapter
265         swipeToRefreshSpinner.adapter = swipeToRefreshArrayAdapter
266         webViewThemeSpinner.adapter = webViewThemeArrayAdapter
267         wideViewportSpinner.adapter = wideViewportArrayAdapter
268         displayWebpageImagesSpinner.adapter = displayImagesArrayAdapter
269
270         // Create a spannable string builder for each TextView that needs multiple colors of text.
271         val savedSslIssuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + savedSslIssuedToCNameString)
272         val savedSslIssuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + savedSslIssuedToONameString)
273         val savedSslIssuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + savedSslIssuedToUNameString)
274         val savedSslIssuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + savedSslIssuedByCNameString)
275         val savedSslIssuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + savedSslIssuedByONameString)
276         val savedSslIssuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + savedSslIssuedByUNameString)
277
278         // Create the date spannable string builders.
279         val savedSslStartDateStringBuilder: SpannableStringBuilder = if (savedSslStartDate == null)
280             SpannableStringBuilder(startDateLabel)
281         else
282             SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslStartDate))
283
284         val savedSslEndDateStringBuilder: SpannableStringBuilder = if (savedSslEndDate == null)
285             SpannableStringBuilder(endDateLabel)
286         else
287             SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslEndDate))
288
289         // Create the color spans.
290         val blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
291         val redColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.red_text))
292
293         // Set the domain name from the the database cursor.
294         domainNameEditText.setText(domainNameString)
295
296         // Update the certificates' Common Name color when the domain name text changes.
297         domainNameEditText.addTextChangedListener(object : TextWatcher {
298             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
299                 // Do nothing.
300             }
301
302             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
303                 // Do nothing.
304             }
305
306             override fun afterTextChanged(s: Editable) {
307                 // Get the new domain name.
308                 val newDomainName = domainNameEditText.text.toString()
309
310                 // Check the saved SSL certificate against the new domain name.
311                 val savedSslMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, savedSslIssuedToCNameString)
312
313                 // Create a spannable string builder for the saved certificate's Common Name.
314                 val savedSslCNameStringBuilder = SpannableStringBuilder(cNameLabel + savedSslIssuedToCNameString)
315
316                 // Format the saved certificate's Common Name color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
317                 if (savedSslMatchesNewDomainName) {
318                     savedSslCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, savedSslCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
319                 } else {
320                     savedSslCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, savedSslCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
321                 }
322
323                 // Update the saved SSL issued to CName text view.
324                 savedSslIssuedToCNameTextView.text = savedSslCNameStringBuilder
325
326                 // Update the current website certificate if it exists.
327                 if (DomainsActivity.sslIssuedToCName != null) {
328                     // Check the current website certificate against the new domain name.
329                     val currentSslMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, DomainsActivity.sslIssuedToCName)
330
331                     // Create a spannable string builder for the current website certificate's Common Name.
332                     val currentSslCNameStringBuilder = SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedToCName)
333
334                     // Format the current certificate Common Name color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
335                     if (currentSslMatchesNewDomainName) {
336                         currentSslCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, currentSslCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
337                     } else {
338                         currentSslCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, currentSslCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
339                     }
340
341                     // Update the current SSL issued to CName text view.
342                     currentSslIssuedToCNameTextView.text = currentSslCNameStringBuilder
343                 }
344             }
345         })
346
347         // Set the switch positions.
348         javaScriptSwitch.isChecked = javaScriptInt == 1
349         cookiesSwitch.isChecked = cookiesInt == 1
350         domStorageSwitch.isChecked = domStorageInt == 1
351         formDataSwitch.isChecked = formDataInt == 1 // Form data can be removed once the minimum API >= 26.
352         easyListSwitch.isChecked = easyListInt == 1
353         easyPrivacySwitch.isChecked = easyPrivacyInt == 1
354         fanboysAnnoyanceListSwitch.isChecked = fanboysAnnoyanceListInt == 1
355         fanboysSocialBlockingListSwitch.isChecked = fanboysSocialBlockingListInt == 1
356         ultraListSwitch.isChecked = ultraListInt == 1
357         ultraPrivacySwitch.isChecked = ultraPrivacyInt == 1
358         blockAllThirdPartyRequestsSwitch.isChecked = blockAllThirdPartyRequestsInt == 1
359         pinnedSslCertificateSwitch.isChecked = pinnedSslCertificateInt == 1
360         pinnedIpAddressesSwitch.isChecked = pinnedIpAddressesInt == 1
361
362         // Set the switch icon colors.
363         cookiesImageView.isSelected = cookiesInt == 1
364         domStorageImageView.isSelected = domStorageInt == 1
365         formDataImageView.isSelected = formDataInt == 1 // Form data can be removed once the minimum API >= 26.
366         easyListImageView.isSelected = easyListInt == 1
367         easyPrivacyImageView.isSelected = easyPrivacyInt == 1
368         fanboysAnnoyanceListImageView.isSelected = fanboysAnnoyanceListInt == 1
369         fanboysSocialBlockingListImageView.isSelected = fanboysSocialBlockingListInt == 1
370         ultraListImageView.isSelected = ultraListInt == 1
371         ultraPrivacyImageView.isSelected = ultraPrivacyInt == 1
372         blockAllThirdPartyRequestsImageView.isSelected = blockAllThirdPartyRequestsInt == 1
373         pinnedSslCertificateImageView.isSelected = pinnedSslCertificateInt == 1
374         pinnedIpAddressesImageView.isSelected = pinnedIpAddressesInt == 1
375
376         // Set the JavaScript icon.
377         if (javaScriptInt == 1)
378             javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.javascript_enabled, null))
379         else
380             javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.privacy_mode, null))
381
382         // Set the DOM storage switch status based on the JavaScript status.
383         domStorageSwitch.isEnabled = (javaScriptInt == 1)
384
385         // Set the DOM storage icon ghosted status based on the JavaScript status.
386         domStorageImageView.isEnabled = (javaScriptInt == 1)
387
388         // Set the form data visibility.  Form data can be removed once the minimum API >= 26.
389         if (Build.VERSION.SDK_INT >= 26) {
390             // Hide the form data image view and switch.
391             formDataImageView.visibility = View.GONE
392             formDataSwitch.visibility = View.GONE
393         }
394
395         // Set Fanboy's Social Blocking List switch status based on the Annoyance List status.
396         fanboysSocialBlockingListSwitch.isEnabled = (fanboysAnnoyanceListInt == 0)
397
398         // Set the Social Blocking List icon ghosted status based on the Annoyance List status.
399         fanboysSocialBlockingListImageView.isEnabled = (fanboysAnnoyanceListInt == 0)
400
401         // Inflated a WebView to get the default user agent.
402         // `@SuppressLint("InflateParams")` removes the warning about using `null` as the `ViewGroup`, which in this case makes sense because the bare WebView should not be displayed on the screen.
403         @SuppressLint("InflateParams") val bareWebViewLayout = inflater.inflate(R.layout.bare_webview, null, false)
404         val bareWebView = bareWebViewLayout.findViewById<WebView>(R.id.bare_webview)
405         val webViewDefaultUserAgentString = bareWebView.settings.userAgentString
406
407         // Get a handle for the user agent array adapter.  This array does not contain the `System default` entry.
408         val userAgentNamesArray = ArrayAdapter.createFromResource(requireContext(), R.array.user_agent_names, R.layout.spinner_item)
409
410         // Get the positions of the user agent and the default user agent.
411         val userAgentArrayPosition = userAgentNamesArray.getPosition(currentUserAgentName)
412         val defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName)
413
414         // Get a handle for the user agent data array.  This array does not contain the `System default` entry.
415         val userAgentDataArray = resources.getStringArray(R.array.user_agent_data)
416
417         // Set the user agent text.
418         if (currentUserAgentName == getString(R.string.system_default_user_agent)) {  // Use the system default user agent.
419             // Set the user agent according to the system default.
420             when (defaultUserAgentArrayPosition) {
421                 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
422                 MainWebViewActivity.UNRECOGNIZED_USER_AGENT -> userAgentTextView.text = defaultUserAgentName
423
424                 // Display the WebView default user agent.
425                 MainWebViewActivity.SETTINGS_WEBVIEW_DEFAULT_USER_AGENT -> userAgentTextView.text = webViewDefaultUserAgentString
426
427                 // Display the custom user agent.
428                 MainWebViewActivity.SETTINGS_CUSTOM_USER_AGENT -> userAgentTextView.text = defaultCustomUserAgentString
429
430                 // Get the user agent string from the user agent data array.
431                 else -> userAgentTextView.text = userAgentDataArray[defaultUserAgentArrayPosition]
432             }
433         } else if (userAgentArrayPosition == MainWebViewActivity.UNRECOGNIZED_USER_AGENT || currentUserAgentName == getString(R.string.custom_user_agent)) {
434             // A custom user agent is stored in the current user agent name.  The second check is necessary in case the user did not change the default custom text.
435             // Set the user agent spinner to `Custom user agent`.
436             userAgentSpinner.setSelection(MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT)
437
438             // Hide the user agent text view.
439             userAgentTextView.visibility = View.GONE
440
441             // Show the custom user agent edit text and set the current user agent name as the text.
442             customUserAgentEditText.visibility = View.VISIBLE
443             customUserAgentEditText.setText(currentUserAgentName)
444         } else {  // The user agent name contains one of the canonical user agents.
445             // Set the user agent spinner selection.  The spinner has one more entry at the beginning than the user agent data array, so the position must be incremented.
446             userAgentSpinner.setSelection(userAgentArrayPosition + 1)
447
448             // Show the user agent text view.
449             userAgentTextView.visibility = View.VISIBLE
450
451             // Hide the custom user agent edit text.
452             customUserAgentEditText.visibility = View.GONE
453
454             // Set the user agent text.
455             if (userAgentArrayPosition == MainWebViewActivity.DOMAINS_WEBVIEW_DEFAULT_USER_AGENT) {  // The WebView default user agent is selected.
456                 // Display the WebView default user agent.
457                 userAgentTextView.text = webViewDefaultUserAgentString
458             } else {  // A user agent besides the default is selected.
459                 // Get the user agent string from the user agent data array.  The spinner has one more entry at the beginning than the user agent data array, so the position must be incremented.
460                 userAgentTextView.text = userAgentDataArray[userAgentArrayPosition + 1]
461             }
462         }
463
464         // Open the user agent spinner when the text view is clicked.
465         userAgentTextView.setOnClickListener { userAgentSpinner.performClick() }
466
467         // Display the font size settings.
468         if (fontSizeInt == 0) {  // `0` is the code for system default font size.
469             // Set the font size to the system default.
470             fontSizeSpinner.setSelection(0)
471
472             // Show the default font size text view.
473             defaultFontSizeTextView.visibility = View.VISIBLE
474
475             // Hide the custom font size edit text.
476             customFontSizeEditText.visibility = View.GONE
477
478             // Set the default font size as the text of the custom font size edit text.  This way, if the user switches to custom it will already be populated.
479             customFontSizeEditText.setText(defaultFontSizeString)
480         } else {  // A custom font size is selected.
481             // Set the spinner to the custom font size.
482             fontSizeSpinner.setSelection(1)
483
484             // Hide the default font size text view.
485             defaultFontSizeTextView.visibility = View.GONE
486
487             // Show the custom font size edit text.
488             customFontSizeEditText.visibility = View.GONE
489
490             // Set the custom font size.
491             customFontSizeEditText.setText(fontSizeInt.toString())
492         }
493
494         // Initialize the default font size percentage string.
495         val defaultFontSizePercentageString = "$defaultFontSizeString%"
496
497         // Set the default font size text in the text view.
498         defaultFontSizeTextView.text = defaultFontSizePercentageString
499
500         // Open the font size spinner when the text view is clicked.
501         defaultFontSizeTextView.setOnClickListener { fontSizeSpinner.performClick() }
502
503         // Select the swipe-to-refresh selection in the spinner.
504         swipeToRefreshSpinner.setSelection(swipeToRefreshInt)
505
506         // Set the swipe-to-refresh text.
507         if (defaultSwipeToRefresh)
508             swipeToRefreshTextView.text = swipeToRefreshArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED)
509         else
510             swipeToRefreshTextView.text = swipeToRefreshArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED)
511
512         // Set the swipe-to-refresh icon and text view settings.
513         when (swipeToRefreshInt) {
514             DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
515                 // Set the icon color.
516                 swipeToRefreshImageView.isSelected = defaultSwipeToRefresh
517
518                 // Show the swipe-to-refresh text view.
519                 swipeToRefreshTextView.visibility = View.VISIBLE
520             }
521
522             DomainsDatabaseHelper.ENABLED -> {
523                 // Set the icon color.
524                 swipeToRefreshImageView.isSelected = true
525
526                 // Hide the swipe-to-refresh text view.
527                 swipeToRefreshTextView.visibility = View.GONE
528             }
529
530             DomainsDatabaseHelper.DISABLED -> {
531                 // Set the icon color.
532                 swipeToRefreshImageView.isSelected = false
533
534                 // Hide the swipe-to-refresh text view.
535                 swipeToRefreshTextView.visibility = View.GONE
536             }
537         }
538
539         // Open the swipe-to-refresh spinner when the text view is clicked.
540         swipeToRefreshTextView.setOnClickListener { swipeToRefreshSpinner.performClick() }
541
542         // Get the WebView theme string arrays.
543         val webViewThemeStringArray = resources.getStringArray(R.array.webview_theme_array)
544         val webViewThemeEntryValuesStringArray = resources.getStringArray(R.array.webview_theme_entry_values)
545
546         // Get the WebView theme entry number that matches the current WebView theme.
547         val appWebViewThemeEntryNumber = when (defaultWebViewTheme) {
548             webViewThemeEntryValuesStringArray[1] -> { 1 }  // The light theme is selected.
549             webViewThemeEntryValuesStringArray[2] -> { 2 }  // The dark theme is selected.
550             else -> { 0 }  // The system default theme is selected.
551         }
552
553         // Select the WebView theme in the spinner.
554         webViewThemeSpinner.setSelection(webViewThemeInt)
555
556         // Set the WebView theme text.
557         if (appWebViewThemeEntryNumber == DomainsDatabaseHelper.SYSTEM_DEFAULT) {  // The app WebView theme is system default.
558             // Set the text according to the current UI theme.
559             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO)
560                 webViewThemeTextView.text = webViewThemeStringArray[DomainsDatabaseHelper.LIGHT_THEME]
561             else
562                 webViewThemeTextView.text = webViewThemeStringArray[DomainsDatabaseHelper.DARK_THEME]
563         } else {  // The app WebView theme is not system default.
564             // Set the text according to the app WebView theme.
565             webViewThemeTextView.text = webViewThemeStringArray[appWebViewThemeEntryNumber]
566         }
567
568         // Set the WebView theme icon and text visibility.
569         when (webViewThemeInt) {
570             DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
571                 // Set the icon color.
572                 when (appWebViewThemeEntryNumber) {
573                     DomainsDatabaseHelper.SYSTEM_DEFAULT -> webViewThemeImageView.isSelected = (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO)
574                     DomainsDatabaseHelper.LIGHT_THEME -> webViewThemeImageView.isSelected = true
575                     DomainsDatabaseHelper.DARK_THEME -> webViewThemeImageView.isSelected = false
576                 }
577
578                 // Show the WebView theme text view.
579                 webViewThemeTextView.visibility = View.VISIBLE
580             }
581
582             DomainsDatabaseHelper.LIGHT_THEME -> {
583                 // Set the icon color.
584                 webViewThemeImageView.isSelected = true
585
586                 // Hide the WebView theme text view.
587                 webViewThemeTextView.visibility = View.GONE
588             }
589
590             DomainsDatabaseHelper.DARK_THEME -> {
591                 // Set the icon color.
592                 webViewThemeImageView.isSelected = false
593
594                 // Hide the WebView theme text view.
595                 webViewThemeTextView.visibility = View.GONE
596             }
597         }
598
599         // Open the WebView theme spinner when the text view is clicked.
600         webViewThemeTextView.setOnClickListener { webViewThemeSpinner.performClick() }
601
602         // Select the wide viewport in the spinner.
603         wideViewportSpinner.setSelection(wideViewportInt)
604
605         // Set the default wide viewport text.
606         if (defaultWideViewport)
607             wideViewportTextView.text = wideViewportArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED)
608         else wideViewportTextView.text = wideViewportArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED)
609
610         // Set the wide viewport icon and text view settings.
611         when (wideViewportInt) {
612             DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
613                 // Set the icon color.
614                 wideViewportImageView.isSelected = defaultWideViewport
615
616                 // Show the wide viewport text view.
617                 wideViewportTextView.visibility = View.VISIBLE
618             }
619
620             DomainsDatabaseHelper.ENABLED -> {
621                 // Set the icon color.
622                 wideViewportImageView.isSelected = true
623
624                 // Hide the wide viewport text view.
625                 wideViewportTextView.visibility = View.GONE
626             }
627
628             DomainsDatabaseHelper.DISABLED -> {
629                 // Set the icon color.
630                 wideViewportImageView.isSelected = false
631
632                 // Hide the wide viewport text view.
633                 wideViewportTextView.visibility = View.GONE
634             }
635         }
636
637         // Open the wide viewport spinner when the text view is clicked.
638         wideViewportTextView.setOnClickListener { wideViewportSpinner.performClick() }
639
640         // Display the website images mode in the spinner.
641         displayWebpageImagesSpinner.setSelection(displayImagesInt)
642
643         // Set the default display images text.
644         if (defaultDisplayWebpageImages)
645             displayImagesTextView.text = displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.ENABLED)
646         else
647             displayImagesTextView.text = displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.DISABLED)
648
649         // Set the display website images icon and text view settings.
650         when (displayImagesInt) {
651             DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
652                 // Set the icon color.
653                 displayWebpageImagesImageView.isSelected = defaultDisplayWebpageImages
654
655                 // Show the display images text view.
656                 displayImagesTextView.visibility = View.VISIBLE
657             }
658
659             DomainsDatabaseHelper.ENABLED -> {
660                 // Set the icon color.
661                 displayWebpageImagesImageView.isSelected = true
662
663                 // Hide the display images text view.
664                 displayImagesTextView.visibility = View.GONE
665             }
666
667             DomainsDatabaseHelper.DISABLED -> {
668                 // Set the icon color.
669                 displayWebpageImagesImageView.isSelected = false
670
671                 // Hide the display images text view.
672                 displayImagesTextView.visibility = View.GONE
673             }
674         }
675
676         // Open the display images spinner when the text view is clicked.
677         displayImagesTextView.setOnClickListener { displayWebpageImagesSpinner.performClick() }
678
679         // Store the current date.
680         val currentDate = Calendar.getInstance().time
681
682         // Setup the string builders to display the general certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
683         savedSslIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, savedSslIssuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
684         savedSslIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, savedSslIssuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
685         savedSslIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, savedSslIssuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
686         savedSslIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, savedSslIssuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
687         savedSslIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, savedSslIssuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
688
689         // Check the certificate Common Name against the domain name.
690         val savedSslCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, savedSslIssuedToCNameString)
691
692         // Format the issued to Common Name color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
693         if (savedSslCommonNameMatchesDomainName)
694             savedSslIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, savedSslIssuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
695         else
696             savedSslIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, savedSslIssuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
697
698         //  Format the start date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
699         if (savedSslStartDate != null && savedSslStartDate.after(currentDate))  // The certificate start date is in the future.
700             savedSslStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, savedSslStartDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
701         else  // The certificate start date is in the past.
702             savedSslStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, savedSslStartDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
703
704         // Format the end date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
705         if (savedSslEndDate != null && savedSslEndDate.before(currentDate))  // The certificate end date is in the past.
706             savedSslEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, savedSslEndDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
707         else  // The certificate end date is in the future.
708             savedSslEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, savedSslEndDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
709
710         // Display the saved website SSL certificate strings.
711         savedSslIssuedToCNameTextView.text = savedSslIssuedToCNameStringBuilder
712         savedSslIssuedToONameTextView.text = savedSslIssuedToONameStringBuilder
713         savedSslIssuedToUNameTextView.text = savedSslIssuedToUNameStringBuilder
714         savedSslIssuedByCNameTextView.text = savedSslIssuedByCNameStringBuilder
715         savedSslIssuedByONameTextView.text = savedSslIssuedByONameStringBuilder
716         savedSslIssuedByUNameTextView.text = savedSslIssuedByUNameStringBuilder
717         savedSslStartDateTextView.text = savedSslStartDateStringBuilder
718         savedSslEndDateTextView.text = savedSslEndDateStringBuilder
719
720         // Populate the current website SSL certificate if there is one.
721         if (DomainsActivity.sslIssuedToCName != null) {
722             // Get dates from the raw long values.
723             val currentSslStartDate = Date(DomainsActivity.sslStartDateLong)
724             val currentSslEndDate = Date(DomainsActivity.sslEndDateLong)
725
726             // Create a spannable string builder for each text view that needs multiple colors of text.
727             val currentSslIssuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedToCName)
728             val currentSslIssuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + DomainsActivity.sslIssuedToOName)
729             val currentSslIssuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + DomainsActivity.sslIssuedToUName)
730             val currentSslIssuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + DomainsActivity.sslIssuedByCName)
731             val currentSslIssuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + DomainsActivity.sslIssuedByOName)
732             val currentSslIssuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + DomainsActivity.sslIssuedByUName)
733             val currentSslStartDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate))
734             val currentSslEndDateStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate))
735
736             // Setup the string builders to display the general certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
737             currentSslIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, currentSslIssuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
738             currentSslIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, currentSslIssuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
739             currentSslIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, currentSslIssuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
740             currentSslIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, currentSslIssuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
741             currentSslIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, currentSslIssuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
742
743             // Check the certificate Common Name against the domain name.
744             val currentSslCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, DomainsActivity.sslIssuedToCName)
745
746             // Format the issued to Common Name color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
747             if (currentSslCommonNameMatchesDomainName)
748                 currentSslIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, currentSslIssuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
749             else
750                 currentSslIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, currentSslIssuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
751
752             //  Format the start date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
753             if (currentSslStartDate.after(currentDate))  // The certificate start date is in the future.
754                 currentSslStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, currentSslStartDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
755             else  // The certificate start date is in the past.
756                 currentSslStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, currentSslStartDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
757
758             // Format the end date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
759             if (currentSslEndDate.before(currentDate))  // The certificate end date is in the past.
760                 currentSslEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, currentSslEndDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
761             else  // The certificate end date is in the future.
762                 currentSslEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, currentSslEndDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
763
764             // Display the current website SSL certificate strings.
765             currentSslIssuedToCNameTextView.text = currentSslIssuedToCNameStringBuilder
766             currentSslIssuedToONameTextView.text = currentSslIssuedToONameStringBuilder
767             currentSslIssuedToUNameTextView.text = currentSslIssuedToUNameStringBuilder
768             currentSslIssuedByCNameTextView.text = currentSslIssuedByCNameStringBuilder
769             currentSslIssuedByONameTextView.text = currentSslIssuedByONameStringBuilder
770             currentSslIssuedByUNameTextView.text = currentSslIssuedByUNameStringBuilder
771             currentSslStartDateTextView.text = currentSslStartDateStringBuilder
772             currentSslEndDateTextView.text = currentSslEndDateStringBuilder
773         }
774
775         // Set the initial display status of the SSL certificates card views.
776         if (pinnedSslCertificateSwitch.isChecked) {  // An SSL certificate is pinned.
777             // Set the visibility of the saved SSL certificate.
778             if (savedSslIssuedToCNameString == null)
779                 savedSslCardView.visibility = View.GONE
780             else
781                 savedSslCardView.visibility = View.VISIBLE
782
783             // Set the visibility of the current website SSL certificate.
784             if (DomainsActivity.sslIssuedToCName == null) {  // There is no current SSL certificate.
785                 // Hide the SSL certificate.
786                 currentSslCardView.visibility = View.GONE
787
788                 // Show the instruction.
789                 noCurrentWebsiteCertificateTextView.visibility = View.VISIBLE
790             } else {  // There is a current SSL certificate.
791                 // Show the SSL certificate.
792                 currentSslCardView.visibility = View.VISIBLE
793
794                 // Hide the instruction.
795                 noCurrentWebsiteCertificateTextView.visibility = View.GONE
796             }
797
798             // Set the status of the radio buttons and the card view backgrounds.
799             if (savedSslCardView.visibility == View.VISIBLE) {  // The saved SSL certificate is displayed.
800                 // Check the saved SSL certificate radio button.
801                 savedSslCertificateRadioButton.isChecked = true
802
803                 // Uncheck the current website SSL certificate radio button.
804                 currentWebsiteCertificateRadioButton.isChecked = false
805
806                 // Darken the background of the current website SSL certificate linear layout.
807                 currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background)
808             } else if (currentSslCardView.visibility == View.VISIBLE) {  // The saved SSL certificate is hidden but the current website SSL certificate is visible.
809                 // Check the current website SSL certificate radio button.
810                 currentWebsiteCertificateRadioButton.isChecked = true
811
812                 // Uncheck the saved SSL certificate radio button.
813                 savedSslCertificateRadioButton.isChecked = false
814             } else {  // Neither SSL certificate is visible.
815                 // Uncheck both radio buttons.
816                 savedSslCertificateRadioButton.isChecked = false
817                 currentWebsiteCertificateRadioButton.isChecked = false
818             }
819         } else {  // An SSL certificate is not pinned.
820             // Hide the SSl certificates and instructions.
821             savedSslCardView.visibility = View.GONE
822             currentSslCardView.visibility = View.GONE
823             noCurrentWebsiteCertificateTextView.visibility = View.GONE
824
825             // Uncheck the radio buttons.
826             savedSslCertificateRadioButton.isChecked = false
827             currentWebsiteCertificateRadioButton.isChecked = false
828         }
829
830         // Populate the saved and current IP addresses.
831         savedIpAddressesTextView.text = savedIpAddresses
832         currentIpAddressesTextView.text = DomainsActivity.currentIpAddresses
833
834         // Set the initial display status of the IP addresses card views.
835         if (pinnedIpAddressesSwitch.isChecked) {  // IP addresses are pinned.
836             // Set the visibility of the saved IP addresses.
837             if (savedIpAddresses == null)  // There are no saved IP addresses.
838                 savedIpAddressesCardView.visibility = View.GONE
839             else  // There are saved IP addresses.
840                 savedIpAddressesCardView.visibility = View.VISIBLE
841
842             // Set the visibility of the current IP addresses.
843             currentIpAddressesCardView.visibility = View.VISIBLE
844
845             // Set the status of the radio buttons and the card view backgrounds.
846             if (savedIpAddressesCardView.visibility == View.VISIBLE) {  // The saved IP addresses are displayed.
847                 // Check the saved IP addresses radio button.
848                 savedIpAddressesRadioButton.isChecked = true
849
850                 // Uncheck the current IP addresses radio button.
851                 currentIpAddressesRadioButton.isChecked = false
852
853                 // Darken the background of the current IP addresses linear layout.
854                 currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background)
855             } else {  // The saved IP addresses are hidden.
856                 // Check the current IP addresses radio button.
857                 currentIpAddressesRadioButton.isChecked = true
858
859                 // Uncheck the saved IP addresses radio button.
860                 savedIpAddressesRadioButton.isChecked = false
861             }
862         } else {  // IP addresses are not pinned.
863             // Hide the IP addresses card views.
864             savedIpAddressesCardView.visibility = View.GONE
865             currentIpAddressesCardView.visibility = View.GONE
866
867             // Uncheck the radio buttons.
868             savedIpAddressesRadioButton.isChecked = false
869             currentIpAddressesRadioButton.isChecked = false
870         }
871
872
873         // Set the JavaScript switch listener.
874         javaScriptSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
875             // Update the JavaScript icon.
876             if (isChecked)
877                 javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.javascript_enabled, null))
878             else
879                 javaScriptImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.privacy_mode, null))
880
881             // Set the DOM storage switch status.
882             domStorageSwitch.isEnabled = isChecked
883
884             // Set the DOM storage ghosted icon status.
885             domStorageImageView.isEnabled = isChecked
886         }
887
888         // Set the cookies switch listener.
889         cookiesSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
890             // Update the icon color.
891             cookiesImageView.isSelected = isChecked
892         }
893
894         // Set the DOM Storage switch listener.
895         domStorageSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
896             // Update the icon color.
897             domStorageImageView.isSelected = isChecked
898         }
899
900         // Set the form data switch listener.  It can be removed once the minimum API >= 26.
901         if (Build.VERSION.SDK_INT < 26) {
902             formDataSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
903                 // Update the icon color.
904                 formDataImageView.isSelected = isChecked
905             }
906         }
907
908         // Set the EasyList switch listener.
909         easyListSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
910             // Update the icon color.
911             easyListImageView.isSelected = isChecked
912         }
913
914         // Set the EasyPrivacy switch listener.
915         easyPrivacySwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
916             // Update the icon color.
917             easyPrivacyImageView.isSelected = isChecked
918         }
919
920         // Set the Fanboy's Annoyance List switch listener.
921         fanboysAnnoyanceListSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
922             // Update the icon color.
923             fanboysAnnoyanceListImageView.isSelected = isChecked
924
925             // Set Fanboy's Social Blocking List switch position.
926             fanboysSocialBlockingListSwitch.isEnabled = !isChecked
927
928             // Set the Social Blocking List icon ghosted status.
929             fanboysSocialBlockingListImageView.isEnabled = !isChecked
930         }
931
932         // Set the Fanboy's Social Blocking List switch listener.
933         fanboysSocialBlockingListSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
934             // Update the icon color.
935             fanboysSocialBlockingListImageView.isSelected = isChecked
936         }
937
938         // Set the UltraList switch listener.
939         ultraListSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
940             // Update the icon color.
941             ultraListImageView.isSelected = isChecked
942         }
943
944         // Set the UltraPrivacy switch listener.
945         ultraPrivacySwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
946             // Update the icon color.
947             ultraPrivacyImageView.isSelected = isChecked
948         }
949
950         // Set the block all third-party requests switch listener.
951         blockAllThirdPartyRequestsSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
952             // Update the icon color.
953             blockAllThirdPartyRequestsImageView.isSelected = isChecked
954         }
955
956         // Set the user agent spinner listener.
957         userAgentSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
958             override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
959                 // Set the new user agent.
960                 when (position) {
961                     MainWebViewActivity.DOMAINS_SYSTEM_DEFAULT_USER_AGENT -> {
962                         // Show the user agent text view.
963                         userAgentTextView.visibility = View.VISIBLE
964
965                         // Hide the custom user agent edit text.
966                         customUserAgentEditText.visibility = View.GONE
967
968                         // Set the user text.
969                         when (defaultUserAgentArrayPosition) {
970                             // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
971                             MainWebViewActivity.UNRECOGNIZED_USER_AGENT -> userAgentTextView.text = defaultUserAgentName
972
973                             // Display the `WebView` default user agent.
974                             MainWebViewActivity.SETTINGS_WEBVIEW_DEFAULT_USER_AGENT -> userAgentTextView.text = webViewDefaultUserAgentString
975
976                             // Display the custom user agent.
977                             MainWebViewActivity.SETTINGS_CUSTOM_USER_AGENT -> userAgentTextView.text = defaultCustomUserAgentString
978
979                             // Get the user agent string from the user agent data array.
980                             else -> userAgentTextView.text = userAgentDataArray[defaultUserAgentArrayPosition]
981                         }
982                     }
983
984                     MainWebViewActivity.DOMAINS_WEBVIEW_DEFAULT_USER_AGENT -> {
985                         // Show the user agent text view.
986                         userAgentTextView.visibility = View.VISIBLE
987
988                         // Set the user agent text.
989                         userAgentTextView.text = webViewDefaultUserAgentString
990
991                         // Hide the custom user agent EditTex.
992                         customUserAgentEditText.visibility = View.GONE
993                     }
994
995                     MainWebViewActivity.DOMAINS_CUSTOM_USER_AGENT -> {
996                         // Hide the user agent TextView.
997                         userAgentTextView.visibility = View.GONE
998
999                         // Show the custom user agent edit text.
1000                         customUserAgentEditText.visibility = View.VISIBLE
1001
1002                         // Set the current user agent name as the text.
1003                         customUserAgentEditText.setText(currentUserAgentName)
1004                     }
1005
1006                     else -> {
1007                         // Show the user agent text view.
1008                         userAgentTextView.visibility = View.VISIBLE
1009
1010                         // Set the text from the user agent data array, which has one less entry than the spinner, so the position must be decremented.
1011                         userAgentTextView.text = userAgentDataArray[position - 1]
1012
1013                         // Hide the custom user agent edit text.
1014                         customUserAgentEditText.visibility = View.GONE
1015                     }
1016                 }
1017             }
1018
1019             override fun onNothingSelected(parent: AdapterView<*>?) {
1020                 // Do nothing.
1021             }
1022         }
1023
1024         // Set the font size spinner listener.
1025         fontSizeSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
1026             override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
1027                 // Update the font size display options.
1028                 if (position == 0) {  // The system default font size has been selected.
1029                     // Show the default font size text view.
1030                     defaultFontSizeTextView.visibility = View.VISIBLE
1031
1032                     // Hide the custom font size edit text.
1033                     customFontSizeEditText.visibility = View.GONE
1034                 } else {  // A custom font size has been selected.
1035                     // Hide the default font size text view.
1036                     defaultFontSizeTextView.visibility = View.GONE
1037
1038                     // Show the custom font size edit text.
1039                     customFontSizeEditText.visibility = View.VISIBLE
1040                 }
1041             }
1042
1043             override fun onNothingSelected(parent: AdapterView<*>?) {
1044                 // Do nothing.
1045             }
1046         }
1047
1048         // Set the swipe-to-refresh spinner listener.
1049         swipeToRefreshSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
1050             override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
1051                 // Update the icon and the visibility of the text view.
1052                 when (position) {
1053                     DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
1054                         // Set the icon color.
1055                         swipeToRefreshImageView.isSelected = defaultSwipeToRefresh
1056
1057                         // Show the swipe-to-refresh text view.
1058                         swipeToRefreshTextView.visibility = View.VISIBLE
1059                     }
1060
1061                     DomainsDatabaseHelper.ENABLED -> {
1062                         // Set the icon color.
1063                         swipeToRefreshImageView.isSelected = true
1064
1065                         // Hide the swipe-to-refresh text view.
1066                         swipeToRefreshTextView.visibility = View.GONE
1067                     }
1068
1069                     DomainsDatabaseHelper.DISABLED -> {
1070                         // Set the icon color.
1071                         swipeToRefreshImageView.isSelected = false
1072
1073                         // Hide the swipe-to-refresh text view.
1074                         swipeToRefreshTextView.visibility = View.GONE
1075                     }
1076                 }
1077             }
1078
1079             override fun onNothingSelected(parent: AdapterView<*>?) {
1080                 // Do nothing.
1081             }
1082         }
1083
1084         // Set the WebView theme spinner listener.
1085         webViewThemeSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
1086             override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
1087                 // Update the icon and the visibility of the WebView theme text view.
1088                 when (position) {
1089                     DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
1090                         // Set the icon color.
1091                         when (appWebViewThemeEntryNumber) {
1092                             DomainsDatabaseHelper.SYSTEM_DEFAULT -> webViewThemeImageView.isSelected = (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO)
1093                             DomainsDatabaseHelper.LIGHT_THEME -> webViewThemeImageView.isSelected = true
1094                             DomainsDatabaseHelper.DARK_THEME -> webViewThemeImageView.isSelected = false
1095                         }
1096
1097                         // Show the WebView theme text view.
1098                         webViewThemeTextView.visibility = View.VISIBLE
1099                     }
1100
1101                     DomainsDatabaseHelper.LIGHT_THEME -> {
1102                         // Set the icon color.
1103                         webViewThemeImageView.isSelected = true
1104
1105                         // Hide the WebView theme text view.
1106                         webViewThemeTextView.visibility = View.GONE
1107                     }
1108
1109                     DomainsDatabaseHelper.DARK_THEME -> {
1110                         // Set the icon color.
1111                         webViewThemeImageView.isSelected = false
1112
1113                         // Hide the WebView theme text view.
1114                         webViewThemeTextView.visibility = View.GONE
1115                     }
1116                 }
1117             }
1118
1119             override fun onNothingSelected(parent: AdapterView<*>?) {
1120                 // Do nothing.
1121             }
1122         }
1123
1124         // Set the wide viewport spinner listener.
1125         wideViewportSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
1126             override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
1127                 // Update the icon and the visibility of the wide viewport text view.
1128                 when (position) {
1129                     DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
1130                         // Set the icon color.
1131                         wideViewportImageView.isSelected = defaultWideViewport
1132
1133                         // Show the wide viewport text view.
1134                         wideViewportTextView.visibility = View.VISIBLE
1135                     }
1136
1137                     DomainsDatabaseHelper.ENABLED -> {
1138                         // Set the icon color.
1139                         wideViewportImageView.isSelected = true
1140
1141                         // Hide the wide viewport text view.
1142                         wideViewportTextView.visibility = View.GONE
1143                     }
1144
1145                     DomainsDatabaseHelper.DISABLED -> {
1146                         // Set the icon color.
1147                         wideViewportImageView.isSelected = false
1148
1149                         // Hid ethe wide viewport text view.
1150                         wideViewportTextView.visibility = View.GONE
1151                     }
1152                 }
1153             }
1154
1155             override fun onNothingSelected(parent: AdapterView<*>?) {
1156                 // Do nothing.
1157             }
1158         }
1159
1160         // Set the display webpage images spinner listener.
1161         displayWebpageImagesSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
1162             override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
1163                 // Update the icon and the visibility of the display images text view.
1164                 when (position) {
1165                     DomainsDatabaseHelper.SYSTEM_DEFAULT -> {
1166                         // Set the icon color.
1167                         displayWebpageImagesImageView.isSelected = defaultDisplayWebpageImages
1168
1169                         // Show the display images text view.
1170                         displayImagesTextView.visibility = View.VISIBLE
1171                     }
1172
1173                     DomainsDatabaseHelper.ENABLED -> {
1174                         // Set the icon color.
1175                         displayWebpageImagesImageView.isSelected = true
1176
1177                         // Hide the display images text view.
1178                         displayImagesTextView.visibility = View.GONE
1179                     }
1180
1181                     DomainsDatabaseHelper.DISABLED -> {
1182                         // Set the icon color.
1183                         displayWebpageImagesImageView.isSelected = false
1184
1185                         // Hide the display images text view.
1186                         displayImagesTextView.visibility = View.GONE
1187                     }
1188                 }
1189             }
1190
1191             override fun onNothingSelected(parent: AdapterView<*>?) {
1192                 // Do nothing.
1193             }
1194         }
1195
1196         // Set the pinned SSL certificate switch listener.
1197         pinnedSslCertificateSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
1198             // Update the icon color.
1199             pinnedSslCertificateImageView.isSelected = isChecked
1200
1201             // Update the views.
1202             if (isChecked) {  // SSL certificate pinning is enabled.
1203                 // Update the visibility of the saved SSL certificate.
1204                 if (savedSslIssuedToCNameString == null)
1205                     savedSslCardView.visibility = View.GONE
1206                 else
1207                     savedSslCardView.visibility = View.VISIBLE
1208
1209                 // Update the visibility of the current website SSL certificate.
1210                 if (DomainsActivity.sslIssuedToCName == null) {
1211                     // Hide the SSL certificate.
1212                     currentSslCardView.visibility = View.GONE
1213
1214                     // Show the instruction.
1215                     noCurrentWebsiteCertificateTextView.visibility = View.VISIBLE
1216                 } else {
1217                     // Show the SSL certificate.
1218                     currentSslCardView.visibility = View.VISIBLE
1219
1220                     // Hide the instruction.
1221                     noCurrentWebsiteCertificateTextView.visibility = View.GONE
1222                 }
1223
1224                 // Set the status of the radio buttons.
1225                 if (savedSslCardView.visibility == View.VISIBLE) {  // The saved SSL certificate is displayed.
1226                     // Check the saved SSL certificate radio button.
1227                     savedSslCertificateRadioButton.isChecked = true
1228
1229                     // Uncheck the current website SSL certificate radio button.
1230                     currentWebsiteCertificateRadioButton.isChecked = false
1231
1232                     // Set the background of the saved SSL certificate linear layout to be transparent.
1233                     savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent)
1234
1235                     // Darken the background of the current website SSL certificate linear layout according to the theme.
1236                     currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background)
1237
1238                     // Scroll to the current website SSL certificate card.
1239                     savedSslCardView.parent.requestChildFocus(savedSslCardView, savedSslCardView)
1240                 } else if (currentSslCardView.visibility == View.VISIBLE) {  // The saved SSL certificate is hidden but the current website SSL certificate is visible.
1241                     // Check the current website SSL certificate radio button.
1242                     currentWebsiteCertificateRadioButton.isChecked = true
1243
1244                     // Uncheck the saved SSL certificate radio button.
1245                     savedSslCertificateRadioButton.isChecked = false
1246
1247                     // Set the background of the current website SSL certificate linear layout to be transparent.
1248                     currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent)
1249
1250                     // Darken the background of the saved SSL certificate linear layout according to the theme.
1251                     savedSslCertificateLinearLayout.setBackgroundResource(R.color.translucent_background)
1252
1253                     // Scroll to the current website SSL certificate card.
1254                     currentSslCardView.parent.requestChildFocus(currentSslCardView, currentSslCardView)
1255                 } else {  // Neither SSL certificate is visible.
1256                     // Uncheck both radio buttons.
1257                     savedSslCertificateRadioButton.isChecked = false
1258                     currentWebsiteCertificateRadioButton.isChecked = false
1259
1260                     // Scroll to the current website SSL certificate card.
1261                     noCurrentWebsiteCertificateTextView.parent.requestChildFocus(noCurrentWebsiteCertificateTextView, noCurrentWebsiteCertificateTextView)
1262                 }
1263             } else {  // SSL certificate pinning is disabled.
1264                 // Hide the SSl certificates and instructions.
1265                 savedSslCardView.visibility = View.GONE
1266                 currentSslCardView.visibility = View.GONE
1267                 noCurrentWebsiteCertificateTextView.visibility = View.GONE
1268
1269                 // Uncheck the radio buttons.
1270                 savedSslCertificateRadioButton.isChecked = false
1271                 currentWebsiteCertificateRadioButton.isChecked = false
1272             }
1273         }
1274
1275         // Set the saved SSL card view listener.
1276         savedSslCardView.setOnClickListener {
1277             // Check the saved SSL certificate radio button.
1278             savedSslCertificateRadioButton.isChecked = true
1279
1280             // Uncheck the current website SSL certificate radio button.
1281             currentWebsiteCertificateRadioButton.isChecked = false
1282
1283             // Set the background of the saved SSL certificate linear layout to be transparent.
1284             savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent)
1285
1286             // Darken the background of the current website SSL certificate linear layout.
1287             currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background)
1288         }
1289
1290         // Set the saved SSL certificate radio button listener.
1291         savedSslCertificateRadioButton.setOnClickListener {
1292             // Check the saved SSL certificate radio button.
1293             savedSslCertificateRadioButton.isChecked = true
1294
1295             // Uncheck the current website SSL certificate radio button.
1296             currentWebsiteCertificateRadioButton.isChecked = false
1297
1298             // Set the background of the saved SSL certificate linear layout to be transparent.
1299             savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent)
1300
1301             // Darken the background of the current website SSL certificate linear layout.
1302             currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.translucent_background)
1303         }
1304
1305         // Set the current SSL card view listener.
1306         currentSslCardView.setOnClickListener {
1307             // Check the current website SSL certificate radio button.
1308             currentWebsiteCertificateRadioButton.isChecked = true
1309
1310             // Uncheck the saved SSL certificate radio button.
1311             savedSslCertificateRadioButton.isChecked = false
1312
1313             // Set the background of the current website SSL certificate linear layout to be transparent.
1314             currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent)
1315
1316             // Darken the background of the saved SSL certificate linear layout.
1317             savedSslCertificateLinearLayout.setBackgroundResource(R.color.translucent_background)
1318         }
1319
1320         // Set the current website certificate radio button listener.
1321         currentWebsiteCertificateRadioButton.setOnClickListener {
1322             // Check the current website SSL certificate radio button.
1323             currentWebsiteCertificateRadioButton.isChecked = true
1324
1325             // Uncheck the saved SSL certificate radio button.
1326             savedSslCertificateRadioButton.isChecked = false
1327
1328             // Set the background of the current website SSL certificate linear layout to be transparent.
1329             currentWebsiteCertificateLinearLayout.setBackgroundResource(R.color.transparent)
1330
1331             // Darken the background of the saved SSL certificate linear layout.
1332             savedSslCertificateLinearLayout.setBackgroundResource(R.color.translucent_background)
1333         }
1334
1335         // Set the pinned IP addresses switch listener.
1336         pinnedIpAddressesSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
1337             // Update the icon color.
1338             pinnedIpAddressesImageView.isSelected = isChecked
1339
1340             // Update the views.
1341             if (isChecked) {  // IP addresses pinning is enabled.
1342                 // Update the visibility of the saved IP addresses card view.
1343                 if (savedIpAddresses == null)
1344                     savedIpAddressesCardView.visibility = View.GONE
1345                 else
1346                     savedIpAddressesCardView.visibility = View.VISIBLE
1347
1348                 // Show the current IP addresses card view.
1349                 currentIpAddressesCardView.visibility = View.VISIBLE
1350
1351                 // Set the status of the radio buttons.
1352                 if (savedIpAddressesCardView.visibility == View.VISIBLE) {  // The saved IP addresses are visible.
1353                     // Check the saved IP addresses radio button.
1354                     savedIpAddressesRadioButton.isChecked = true
1355
1356                     // Uncheck the current IP addresses radio button.
1357                     currentIpAddressesRadioButton.isChecked = false
1358
1359                     // Set the background of the saved IP addresses linear layout to be transparent.
1360                     savedSslCertificateLinearLayout.setBackgroundResource(R.color.transparent)
1361
1362                     // Darken the background of the current IP addresses linear layout.
1363                     currentIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background)
1364                 } else {  // The saved IP addresses are not visible.
1365                     // Check the current IP addresses radio button.
1366                     currentIpAddressesRadioButton.isChecked = true
1367
1368                     // Uncheck the saved IP addresses radio button.
1369                     savedIpAddressesRadioButton.isChecked = false
1370
1371                     // Set the background of the current IP addresses linear layout to be transparent.
1372                     currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent)
1373
1374                     // Darken the background of the saved IP addresses linear layout.
1375                     savedIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background)
1376                 }
1377
1378                 // Scroll to the bottom of the card views.
1379                 currentIpAddressesCardView.parent.requestChildFocus(currentIpAddressesCardView, currentIpAddressesCardView)
1380             } else {  // IP addresses pinning is disabled.
1381                 // Hide the IP addresses card views.
1382                 savedIpAddressesCardView.visibility = View.GONE
1383                 currentIpAddressesCardView.visibility = View.GONE
1384
1385                 // Uncheck the radio buttons.
1386                 savedIpAddressesRadioButton.isChecked = false
1387                 currentIpAddressesRadioButton.isChecked = false
1388             }
1389         }
1390
1391         // Set the saved IP addresses card view listener.
1392         savedIpAddressesCardView.setOnClickListener {
1393             // Check the saved IP addresses radio button.
1394             savedIpAddressesRadioButton.isChecked = true
1395
1396             // Uncheck the current website IP addresses radio button.
1397             currentIpAddressesRadioButton.isChecked = false
1398
1399             // Set the background of the saved IP addresses linear layout to be transparent.
1400             savedIpAddressesLinearLayout.setBackgroundResource(R.color.transparent)
1401
1402             // Darken the background of the current IP addresses linear layout.
1403                 currentIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background)
1404         }
1405
1406         // Set the saved IP addresses radio button listener.
1407         savedIpAddressesRadioButton.setOnClickListener {
1408             // Check the saved IP addresses radio button.
1409             savedIpAddressesRadioButton.isChecked = true
1410
1411             // Uncheck the current website IP addresses radio button.
1412             currentIpAddressesRadioButton.isChecked = false
1413
1414             // Set the background of the saved IP addresses linear layout to be transparent.
1415             savedIpAddressesLinearLayout.setBackgroundResource(R.color.transparent)
1416
1417             // Darken the background of the current IP addresses linear layout.
1418             currentIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background)
1419         }
1420
1421         // Set the current IP addresses card view listener.
1422         currentIpAddressesCardView.setOnClickListener {
1423             // Check the current IP addresses radio button.
1424             currentIpAddressesRadioButton.isChecked = true
1425
1426             // Uncheck the saved IP addresses radio button.
1427             savedIpAddressesRadioButton.isChecked = false
1428
1429             // Set the background of the current IP addresses linear layout to be transparent.
1430             currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent)
1431
1432             // Darken the background of the saved IP addresses linear layout.
1433             savedIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background)
1434         }
1435
1436         // Set the current IP addresses radio button listener.
1437         currentIpAddressesRadioButton.setOnClickListener {
1438             // Check the current IP addresses radio button.
1439             currentIpAddressesRadioButton.isChecked = true
1440
1441             // Uncheck the saved IP addresses radio button.
1442             savedIpAddressesRadioButton.isChecked = false
1443
1444             // Set the background of the current IP addresses linear layout to be transparent.
1445             currentIpAddressesLinearLayout.setBackgroundResource(R.color.transparent)
1446
1447             // Darken the background of the saved IP addresses linear layout.
1448             savedIpAddressesLinearLayout.setBackgroundResource(R.color.translucent_background)
1449         }
1450
1451         // Set the scroll Y.
1452         domainSettingsScrollView.post { domainSettingsScrollView.scrollY = scrollY }
1453
1454         // Return the domain settings view.
1455         return domainSettingsView
1456     }
1457
1458     private fun checkDomainNameAgainstCertificate(domainName: String?, certificateCommonName: String?): Boolean {
1459         // Initialize the domain names match tracker.
1460         var domainNamesMatch = false
1461
1462         // Check various wildcard permutations if the domain name and the certificate Common Name are not empty.
1463         if ((domainName != null) && (certificateCommonName != null)) {
1464             // Check if the domains match.
1465             if (domainName == certificateCommonName)
1466                 domainNamesMatch = true
1467
1468             // If the domain name starts with a wildcard, check the base domain against all the subdomains of the certificate Common Name.
1469             if (!domainNamesMatch && domainName.startsWith("*.") && domainName.length > 2) {
1470                 // Remove the initial `*.`.
1471                 val baseDomainName = domainName.substring(2)
1472
1473                 // Create a copy of the certificate Common Name to test subdomains.
1474                 var certificateCommonNameSubdomain: String = certificateCommonName
1475
1476                 // Check all the subdomains in the certificate Common Name subdomain against the base domain name.
1477                 while (!domainNamesMatch && certificateCommonNameSubdomain.contains(".")) {  // Stop checking if the domain names match or if there are no more dots.
1478                     // Test the certificate Common Name subdomain against the base domain name.
1479                     if (certificateCommonNameSubdomain == baseDomainName)
1480                         domainNamesMatch = true
1481
1482                     // Strip out the lowest subdomain of the certificate Common Name subdomain.
1483                     certificateCommonNameSubdomain = try {
1484                         certificateCommonNameSubdomain.substring(certificateCommonNameSubdomain.indexOf(".") + 1)
1485                     } catch (e: IndexOutOfBoundsException) {  // The certificate Common Name subdomain ends with a dot.
1486                         ""
1487                     }
1488                 }
1489             }
1490
1491             // If the certificate Common Name starts with a wildcard, check the base common name against all the subdomains of the domain name.
1492             if (!domainNamesMatch && certificateCommonName.startsWith("*.") && certificateCommonName.length > 2) {
1493                 // Remove the initial `*.`.
1494                 val baseCertificateCommonName = certificateCommonName.substring(2)
1495
1496                 // Setup a copy of domain name to test subdomains.
1497                 var domainNameSubdomain: String = domainName
1498
1499                 // Check all the subdomains in the domain name subdomain against the base certificate Common Name.
1500                 while (!domainNamesMatch && domainNameSubdomain.contains(".") && domainNameSubdomain.length > 2) {
1501                     // Test the domain name subdomain  against the base certificate Common Name.
1502                     if (domainNameSubdomain == baseCertificateCommonName)
1503                         domainNamesMatch = true
1504
1505                     // Strip out the lowest subdomain of the domain name subdomain.
1506                     domainNameSubdomain = try {
1507                         domainNameSubdomain.substring(domainNameSubdomain.indexOf(".") + 1)
1508                     } catch (e: IndexOutOfBoundsException) {  // `domainNameSubdomain` ends with a dot.
1509                         ""
1510                     }
1511                 }
1512             }
1513
1514             // If both names start with a wildcard, check if the root of one contains the root of the other.
1515             if (!domainNamesMatch && domainName.startsWith("*.") && domainName.length > 2 && certificateCommonName.startsWith("*.") && certificateCommonName.length > 2) {
1516                 // Remove the wildcards.
1517                 val rootDomainName = domainName.substring(2)
1518                 val rootCertificateCommonName = certificateCommonName.substring(2)
1519
1520                 // Check if one name ends with the contents of the other.  If so, there will be overlap in the their wildcard subdomains.
1521                 if (rootDomainName.endsWith(rootCertificateCommonName) || rootCertificateCommonName.endsWith(rootDomainName))
1522                     domainNamesMatch = true
1523             }
1524         }
1525
1526         return domainNamesMatch
1527     }
1528 }