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