]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt
First wrong button text in View Headers in night theme. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / fragments / AboutVersionFragment.kt
1 /*
2  * Copyright 2016-2024 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.app.Activity
24 import android.app.ActivityManager
25 import android.content.ClipData
26 import android.content.ClipboardManager
27 import android.content.Context
28 import android.content.Intent
29 import android.content.pm.PackageManager
30 import android.os.Build
31 import android.os.Bundle
32 import android.os.Handler
33 import android.os.Looper
34 import android.provider.OpenableColumns
35 import android.text.SpannableStringBuilder
36 import android.text.Spanned
37 import android.text.style.ForegroundColorSpan
38 import android.view.LayoutInflater
39 import android.view.Menu
40 import android.view.MenuInflater
41 import android.view.MenuItem
42 import android.view.View
43 import android.view.ViewGroup
44 import android.webkit.WebView
45 import android.widget.TextView
46
47 import androidx.activity.result.contract.ActivityResultContracts
48 import androidx.fragment.app.Fragment
49
50 import com.google.android.material.snackbar.Snackbar
51
52 import com.stoutner.privacybrowser.BuildConfig
53 import com.stoutner.privacybrowser.R
54 import com.stoutner.privacybrowser.coroutines.SaveAboutVersionImageCoroutine
55
56 import kotlinx.coroutines.CoroutineScope
57 import kotlinx.coroutines.Dispatchers
58 import kotlinx.coroutines.launch
59 import kotlinx.coroutines.withContext
60
61 import java.io.ByteArrayInputStream
62 import java.io.InputStream
63 import java.lang.Exception
64 import java.nio.charset.StandardCharsets
65 import java.security.cert.CertificateException
66 import java.security.cert.CertificateFactory
67 import java.security.cert.X509Certificate
68 import java.text.DateFormat
69 import java.text.NumberFormat
70
71 import kotlin.text.StringBuilder
72
73 // Define the class constants.
74 private const val FILTERLISTS_VERSIONS = "A"
75 private const val SCROLL_Y = "B"
76 private const val MEBIBYTE = 1048576
77
78 class AboutVersionFragment : Fragment() {
79     // Define the class variables.
80     private var updateMemoryUsageBoolean = true
81
82     // Declare the class variables.
83     private lateinit var aboutVersionLayout: View
84     private lateinit var activityManager: ActivityManager
85     private lateinit var appAvailableMemoryLabel: String
86     private lateinit var appConsumedMemoryLabel: String
87     private lateinit var appMaximumMemoryLabel: String
88     private lateinit var appTotalMemoryLabel: String
89     private lateinit var blueColorSpan: ForegroundColorSpan
90     private lateinit var filterListsVersions: Array<String>
91     private lateinit var memoryInfo: ActivityManager.MemoryInfo
92     private lateinit var numberFormat: NumberFormat
93     private lateinit var runtime: Runtime
94     private lateinit var systemAvailableMemoryLabel: String
95     private lateinit var systemConsumedMemoryLabel: String
96     private lateinit var systemTotalMemoryLabel: String
97
98     // Declare the class views.
99     private lateinit var androidTextView: TextView
100     private lateinit var appAvailableMemoryTextView: TextView
101     private lateinit var appConsumedMemoryTextView: TextView
102     private lateinit var appMaximumMemoryTextView: TextView
103     private lateinit var appTotalMemoryTextView: TextView
104     private lateinit var brandTextView: TextView
105     private lateinit var bootloaderTextView: TextView
106     private lateinit var certificateEndDateTextView: TextView
107     private lateinit var certificateIssuerDnTextView: TextView
108     private lateinit var certificateSerialNumberTextView: TextView
109     private lateinit var certificateSignatureAlgorithmTextView: TextView
110     private lateinit var certificateStartDateTextView: TextView
111     private lateinit var certificateSubjectDnTextView: TextView
112     private lateinit var certificateVersionTextView: TextView
113     private lateinit var buildTextView: TextView
114     private lateinit var deviceTextView: TextView
115     private lateinit var easyListTextView: TextView
116     private lateinit var easyPrivacyTextView: TextView
117     private lateinit var fanboyAnnoyanceTextView: TextView
118     private lateinit var fanboySocialTextView: TextView
119     private lateinit var filterListsTextView: TextView
120     private lateinit var hardwareTextView: TextView
121     private lateinit var i2pTextView: TextView
122     private lateinit var kernelTextView: TextView
123     private lateinit var manufacturerTextView: TextView
124     private lateinit var memoryUsageTextView: TextView
125     private lateinit var modelTextView: TextView
126     private lateinit var openKeychainTextView: TextView
127     private lateinit var orbotTextView: TextView
128     private lateinit var packageSignatureTextView: TextView
129     private lateinit var privacyBrowserTextView: TextView
130     private lateinit var radioTextView: TextView
131     private lateinit var securityPatchTextView: TextView
132     private lateinit var softwareTextView: TextView
133     private lateinit var systemAvailableMemoryTextView: TextView
134     private lateinit var systemConsumedMemoryTextView: TextView
135     private lateinit var systemTotalMemoryTextView: TextView
136     private lateinit var versionTextView: TextView
137     private lateinit var ultraListTextView: TextView
138     private lateinit var ultraPrivacyTextView: TextView
139     private lateinit var webViewProviderTextView: TextView
140     private lateinit var webViewVersionTextView: TextView
141
142     companion object {
143         fun createTab(filterListsVersions: Array<String>): AboutVersionFragment {
144             // Create an arguments bundle.
145             val argumentsBundle = Bundle()
146
147             // Store the arguments in the bundle.
148             argumentsBundle.putStringArray(FILTERLISTS_VERSIONS, filterListsVersions)
149
150             // Create a new instance of the tab fragment.
151             val aboutVersionFragment = AboutVersionFragment()
152
153             // Add the arguments bundle to the fragment.
154             aboutVersionFragment.arguments = argumentsBundle
155
156             // Return the new fragment.
157             return aboutVersionFragment
158         }
159     }
160
161     // Define the save about version text activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
162     private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri ->
163         // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
164         if (fileUri != null) {
165             // Get a cursor from the content resolver.
166             val contentResolverCursor = requireActivity().contentResolver.query(fileUri, null, null, null)!!
167
168             // Move to the first row.
169             contentResolverCursor.moveToFirst()
170
171             // Get the file name from the cursor.
172             val fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
173
174             // Close the cursor.
175             contentResolverCursor.close()
176
177             try {
178                 // Get the about version string.
179                 val aboutVersionString = getAboutVersionString()
180
181                 // Open an output stream.
182                 val outputStream = requireActivity().contentResolver.openOutputStream(fileUri)!!
183
184                 // Save about version using a coroutine with Dispatchers.IO.
185                 CoroutineScope(Dispatchers.Main).launch {
186                     withContext(Dispatchers.IO) {
187                         // Write the about version string to the output stream.
188                         outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
189
190                         // Close the output stream.
191                         outputStream.close()
192                     }
193                 }
194
195                 // Display a snackbar with the saved logcat information.
196                 Snackbar.make(aboutVersionLayout, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
197             } catch (exception: Exception) {
198                 // Display a snackbar with the error message.
199                 Snackbar.make(aboutVersionLayout, getString(R.string.error_saving_file, fileNameString, exception.toString()), Snackbar.LENGTH_INDEFINITE).show()
200             }
201         }
202     }
203
204     // Define the save about version image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
205     private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri ->
206         // Save the file if the URI is not null, which happens if the user exits the file picker by pressing back.
207         if (fileUri != null)
208             SaveAboutVersionImageCoroutine.saveImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout))
209     }
210
211     override fun onCreate(savedInstanceState: Bundle?) {
212         // Run the default commands.
213         super.onCreate(savedInstanceState)
214
215         // Store the arguments in class variables.
216         filterListsVersions = requireArguments().getStringArray(FILTERLISTS_VERSIONS)!!
217
218         // Enable the options menu for this fragment.
219         setHasOptionsMenu(true)
220     }
221
222     override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
223         // Inflate the layout.  Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container.  The fragment will take care of attaching the root automatically.
224         aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
225
226         // Get handles for the views.
227         privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
228         versionTextView = aboutVersionLayout.findViewById(R.id.version)
229         hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
230         brandTextView = aboutVersionLayout.findViewById(R.id.brand)
231         manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
232         modelTextView = aboutVersionLayout.findViewById(R.id.model)
233         deviceTextView = aboutVersionLayout.findViewById(R.id.device)
234         bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
235         radioTextView = aboutVersionLayout.findViewById(R.id.radio)
236         softwareTextView = aboutVersionLayout.findViewById(R.id.software)
237         androidTextView = aboutVersionLayout.findViewById(R.id.android)
238         securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
239         buildTextView = aboutVersionLayout.findViewById(R.id.build)
240         kernelTextView = aboutVersionLayout.findViewById(R.id.kernel)
241         webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
242         webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
243         orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
244         i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
245         openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
246         memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
247         appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
248         appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
249         appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
250         appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
251         systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
252         systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
253         systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
254         filterListsTextView = aboutVersionLayout.findViewById(R.id.filterlists)
255         easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
256         easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
257         fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
258         fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
259         ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
260         ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
261         packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
262         certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
263         certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
264         certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
265         certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
266         certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
267         certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
268         certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
269
270         // Setup the labels.
271         val version = getString(R.string.version_code, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
272         val brandLabel = getString(R.string.brand)
273         val manufacturerLabel = getString(R.string.manufacturer)
274         val modelLabel = getString(R.string.model)
275         val deviceLabel = getString(R.string.device)
276         val bootloaderLabel = getString(R.string.bootloader)
277         val androidLabel = getString(R.string.android)
278         val securityPatchLabel = getString(R.string.security_patch)
279         val buildLabel = getString(R.string.build)
280         val kernelLabel = getString(R.string.kernel)
281         val webViewProviderLabel = getString(R.string.webview_provider)
282         val webViewVersionLabel = getString(R.string.webview_version)
283         appConsumedMemoryLabel = getString(R.string.app_consumed_memory)
284         appAvailableMemoryLabel = getString(R.string.app_available_memory)
285         appTotalMemoryLabel = getString(R.string.app_total_memory)
286         appMaximumMemoryLabel = getString(R.string.app_maximum_memory)
287         systemConsumedMemoryLabel = getString(R.string.system_consumed_memory)
288         systemAvailableMemoryLabel = getString(R.string.system_available_memory)
289         systemTotalMemoryLabel = getString(R.string.system_total_memory)
290         val easyListLabel = getString(R.string.easylist_label)
291         val easyPrivacyLabel = getString(R.string.easyprivacy_label)
292         val fanboyAnnoyanceLabel = getString(R.string.fanboys_annoyance_label)
293         val fanboySocialLabel = getString(R.string.fanboys_social_label)
294         val ultraListLabel = getString(R.string.ultralist_label)
295         val ultraPrivacyLabel = getString(R.string.ultraprivacy_label)
296         val issuerDNLabel = getString(R.string.issuer_dn)
297         val subjectDNLabel = getString(R.string.subject_dn)
298         val startDateLabel = getString(R.string.start_date)
299         val endDateLabel = getString(R.string.end_date)
300         val certificateVersionLabel = getString(R.string.certificate_version)
301         val serialNumberLabel = getString(R.string.serial_number)
302         val signatureAlgorithmLabel = getString(R.string.signature_algorithm)
303
304         // Get the current WebView package info.
305         val webViewPackageInfo = WebView.getCurrentWebViewPackage()!!
306
307         // Get the device's information and store it in strings.
308         val brand = Build.BRAND
309         val manufacturer = Build.MANUFACTURER
310         val model = Build.MODEL
311         val device = Build.DEVICE
312         val bootloader = Build.BOOTLOADER
313         val radio = Build.getRadioVersion()
314         val android = getString(R.string.api, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)
315         val securityPatch = Build.VERSION.SECURITY_PATCH
316         val build = Build.DISPLAY
317         val kernel = System.getProperty("os.version")
318         val webViewPackageName = webViewPackageInfo.packageName
319         val webViewVersion = webViewPackageInfo.versionName
320
321         // Get the Orbot version name if Orbot is installed.
322         val orbot: String = try {
323             // Store the version name.
324             requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
325         } catch (exception: PackageManager.NameNotFoundException) {  // Orbot is not installed.
326             // Store an empty string.
327             ""
328         }
329
330         // Get the I2P version name if I2P is installed.
331         val i2p: String = try {
332             // Check to see if the F-Droid flavor is installed.
333             getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
334         } catch (exception: PackageManager.NameNotFoundException) {  // The F-Droid flavor is not installed.
335             try {
336                 // Check to see if the F-Droid flavor is installed.
337                 getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
338             } catch (exception: PackageManager.NameNotFoundException) {  // The Google Play flavor is not installed either.
339                 // Store an empty string.
340                 ""
341             }
342         }
343
344         // Get the OpenKeychain version name if it is installed.
345         val openKeychain: String = try {
346             // Store the version name.
347             requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
348         } catch (exception: PackageManager.NameNotFoundException) {  // OpenKeychain is not installed.
349             // Store an empty string.
350             ""
351         }
352
353         // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
354         val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
355         val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
356         val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
357         val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
358         val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
359         val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
360         val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
361         val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
362         val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
363         val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
364         val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webViewVersion)
365         val easyListStringBuilder = SpannableStringBuilder(easyListLabel + filterListsVersions[0])
366         val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + filterListsVersions[1])
367         val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + filterListsVersions[2])
368         val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + filterListsVersions[3])
369         val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + filterListsVersions[4])
370         val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + filterListsVersions[5])
371
372         // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until the minimum API >= 23.
373         blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
374
375         // Set the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
376         brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
377         manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
378         modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
379         deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
380         bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
381         androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
382         securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
383         buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
384         kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
385         webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
386         webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
387         easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
388         easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389         fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390         fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
391         ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392         ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
393
394         // Display the strings in the text boxes.
395         versionTextView.text = version
396         brandTextView.text = brandStringBuilder
397         manufacturerTextView.text = manufacturerStringBuilder
398         modelTextView.text = modelStringBuilder
399         deviceTextView.text = deviceStringBuilder
400         bootloaderTextView.text = bootloaderStringBuilder
401         androidTextView.text = androidStringBuilder
402         securityPatchTextView.text = securityPatchStringBuilder
403         buildTextView.text = buildStringBuilder
404         kernelTextView.text = kernelStringBuilder
405         webViewProviderTextView.text = webViewProviderStringBuilder
406         webViewVersionTextView.text = webViewVersionStringBuilder
407         easyListTextView.text = easyListStringBuilder
408         easyPrivacyTextView.text = easyPrivacyStringBuilder
409         fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
410         fanboySocialTextView.text = fanboySocialStringBuilder
411         ultraListTextView.text = ultraListStringBuilder
412         ultraPrivacyTextView.text = ultraPrivacyStringBuilder
413
414         // Only populate the radio text view if there is a radio in the device.
415         // Null must be checked because some Samsung tablets report a null value for the radio instead of an empty string.  Grrrr.  <https://redmine.stoutner.com/issues/701>
416         if (radio != null && radio.isNotEmpty()) {
417             // Setup the label.
418             val radioLabel = getString(R.string.radio)
419
420             // Create a spannable string builder.
421             val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
422
423             // Set the span to display the radio in blue.
424             radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
425
426             // Display the string in the text view.
427             radioTextView.text = radioStringBuilder
428         } else {  // This device does not have a radio.
429             // Hide the radio text view.
430             radioTextView.visibility = View.GONE
431         }
432
433         // Only populate the Orbot text view if it is installed.
434         if (orbot.isNotEmpty()) {
435             // Setup the label.
436             val orbotLabel = getString(R.string.orbot)
437
438             // Create a spannable string builder.
439             val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
440
441             // Set the span to display the Orbot version.
442             orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
443
444             // Display the string in the text view.
445             orbotTextView.text = orbotStringBuilder
446         } else {  // Orbot is not installed.
447             // Hide the Orbot text view.
448             orbotTextView.visibility = View.GONE
449         }
450
451         // Only populate the I2P text view if it is installed.
452         if (i2p.isNotEmpty()) {
453             // Setup the label.
454             val i2pLabel = getString(R.string.i2p)
455
456             // Create a spannable string builder.
457             val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
458
459             // Set the span to display the I2P version.
460             i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
461
462             // Display the string in the text view.
463             i2pTextView.text = i2pStringBuilder
464         } else {  // I2P is not installed.
465             // Hide the I2P text view.
466             i2pTextView.visibility = View.GONE
467         }
468
469         // Only populate the OpenKeychain text view if it is installed.
470         if (openKeychain.isNotEmpty()) {
471             // Setup the label.
472             val openKeychainLabel = getString(R.string.openkeychain)
473
474             // Create a spannable string builder.
475             val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
476
477             // Set the span to display the OpenKeychain version.
478             openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
479
480             // Display the string in the text view.
481             openKeychainTextView.text = openKeychainStringBuilder
482         } else {  //OpenKeychain is not installed.
483             // Hide the OpenKeychain text view.
484             openKeychainTextView.visibility = View.GONE
485         }
486
487         // Display the package signature.
488         try {
489             // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
490             // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead.  Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
491             @Suppress("DEPRECATION")
492             @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
493                 .signatures[0]
494
495             // Convert the signature to a byte array input stream.
496             val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
497
498             // Display the certificate information on the screen.
499             try {
500                 // Instantiate a certificate factory.
501                 val certificateFactory = CertificateFactory.getInstance("X509")
502
503                 // Generate an X509 certificate.
504                 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
505
506                 // Store the individual sections of the certificate.
507                 val issuerDNPrincipal = x509Certificate.issuerDN
508                 val subjectDNPrincipal = x509Certificate.subjectDN
509                 val startDate = x509Certificate.notBefore
510                 val endDate = x509Certificate.notAfter
511                 val certificateVersion = x509Certificate.version
512                 val serialNumberBigInteger = x509Certificate.serialNumber
513                 val signatureAlgorithmNameString = x509Certificate.sigAlgName
514
515                 // Create a spannable string builder for each text view that needs multiple colors of text.
516                 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
517                 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
518                 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
519                 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
520                 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
521                 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
522                 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
523
524                 // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
525                 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
526                 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
527                 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
528                 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
529                 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
530                 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
531                 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
532
533                 // Display the strings in the text boxes.
534                 certificateIssuerDnTextView.text = issuerDNStringBuilder
535                 certificateSubjectDnTextView.text = subjectDNStringBuilder
536                 certificateStartDateTextView.text = startDateStringBuilder
537                 certificateEndDateTextView.text = endDataStringBuilder
538                 certificateVersionTextView.text = certificateVersionStringBuilder
539                 certificateSerialNumberTextView.text = serialNumberStringBuilder
540                 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
541             } catch (certificateException: CertificateException) {
542                 // Do nothing if there is a certificate error.
543             }
544
545             // Get a handle for the runtime.
546             runtime = Runtime.getRuntime()
547
548             // Get a handle for the activity manager.
549             activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
550
551             // Instantiate a memory info variable.
552             memoryInfo = ActivityManager.MemoryInfo()
553
554             // Define a number format.
555             numberFormat = NumberFormat.getInstance()
556
557             // Set the minimum and maximum number of fraction digits.
558             numberFormat.minimumFractionDigits = 2
559             numberFormat.maximumFractionDigits = 2
560
561             // Update the memory usage.
562             updateMemoryUsage(requireActivity())
563         } catch (e: PackageManager.NameNotFoundException) {
564             // Do nothing if the package manager says Privacy Browser isn't installed.
565         }
566
567         // Scroll the tab if the saved instance state is not null.
568         if (savedInstanceState != null) {
569             aboutVersionLayout.post {
570                 aboutVersionLayout.scrollY = savedInstanceState.getInt(SCROLL_Y)
571             }
572         }
573
574         // Return the tab layout.
575         return aboutVersionLayout
576     }
577
578     override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
579         // Inflate the about version menu.
580         menuInflater.inflate(R.menu.about_version_options_menu, menu)
581
582         // Run the default commands.
583         super.onCreateOptionsMenu(menu, menuInflater)
584     }
585
586     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
587         // Run the appropriate commands.
588         when (menuItem.itemId) {
589             R.id.copy -> {  // Copy.
590                 // Get the about version string.
591                 val aboutVersionString = getAboutVersionString()
592
593                 // Get a handle for the clipboard manager.
594                 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
595
596                 // Place the about version string in a clip data.
597                 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
598
599                 // Place the clip data on the clipboard.
600                 clipboardManager.setPrimaryClip(aboutVersionClipData)
601
602                 // Display a snackbar if the API <= 32 (Android 12L).  Beginning in Android 13 the OS displays a notification that covers up the snackbar.
603                 if (Build.VERSION.SDK_INT <= 32)
604                     Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
605
606                 // Consume the event.
607                 return true
608             }
609
610             R.id.share -> {  // Share.
611                 // Get the about version string.
612                 val aboutString = getAboutVersionString()
613
614                 // Create a share intent.
615                 val shareIntent = Intent(Intent.ACTION_SEND)
616
617                 // Add the about version string to the intent.
618                 shareIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
619
620                 // Set the MIME type.
621                 shareIntent.type = "text/plain"
622
623                 // Set the intent to open in a new task.
624                 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
625
626                 // Make it so.
627                 startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
628
629                 // Consume the event.
630                 return true
631             }
632
633             R.id.save_text -> {  // Save text.
634                 // Open the file picker.
635                 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
636
637                 // Consume the event.
638                 return true
639             }
640
641             R.id.save_image -> {  // Save image.
642                 // Open the file picker.
643                 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
644
645                 // Consume the event.
646                 return true
647             }
648
649             else -> {  // The home button was selected.
650                 // Run the parents class on return.
651                 return super.onOptionsItemSelected(menuItem)
652             }
653         }
654     }
655
656     override fun onSaveInstanceState(savedInstanceState: Bundle) {
657         // Run the default commands.
658         super.onSaveInstanceState(savedInstanceState)
659
660         // Save the scroll position.
661         savedInstanceState.putInt(SCROLL_Y, aboutVersionLayout.scrollY)
662     }
663
664     override fun onPause() {
665         // Run the default commands.
666         super.onPause()
667
668         // Pause the updating of the memory usage.
669         updateMemoryUsageBoolean = false
670     }
671
672     override fun onResume() {
673         // Run the default commands.
674         super.onResume()
675
676         // Resume the updating of the memory usage.
677         updateMemoryUsageBoolean = true
678     }
679
680     private fun updateMemoryUsage(activity: Activity) {
681         try {
682             // Update the memory usage if enabled.
683             if (updateMemoryUsageBoolean) {
684                 // Populate the memory info variable.
685                 activityManager.getMemoryInfo(memoryInfo)
686
687                 // Get the app memory information.
688                 val appAvailableMemoryLong = runtime.freeMemory()
689                 val appTotalMemoryLong = runtime.totalMemory()
690                 val appMaximumMemoryLong = runtime.maxMemory()
691
692                 // Calculate the app consumed memory.
693                 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
694
695                 // Get the system memory information.
696                 val systemTotalMemoryLong = memoryInfo.totalMem
697                 val systemAvailableMemoryLong = memoryInfo.availMem
698
699                 // Calculate the system consumed memory.
700                 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
701
702                 // Convert the memory information into mebibytes.
703                 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
704                 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
705                 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
706                 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
707                 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
708                 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
709                 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
710
711                 // Get the mebibyte string.
712                 val mebibyte = getString(R.string.mebibyte)
713
714                 // Calculate the mebibyte length.
715                 val mebibyteLength = mebibyte.length
716
717                 // Create spannable string builders.
718                 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
719                 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
720                 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
721                 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
722                 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
723                 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
724                 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
725
726                 // Setup the spans to display the memory information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
727                 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
728                 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
729                 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
730                 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
731                 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
732                 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
733                 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
734
735                 // Display the string in the text boxes.
736                 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
737                 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
738                 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
739                 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
740                 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
741                 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
742                 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
743             }
744
745             // Schedule another memory update if the activity has not been destroyed.
746             if (!activity.isDestroyed) {
747                 // Create a handler to update the memory usage.
748                 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
749
750                 // Create a runnable to update the memory usage.
751                 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
752
753                 // Update the memory usage after 1000 milliseconds
754                 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
755             }
756         } catch (exception: Exception) {
757             // Do nothing.
758         }
759     }
760
761     private fun getAboutVersionString(): String {
762         // Initialize an about version string builder.
763         val aboutVersionStringBuilder = StringBuilder()
764
765         // Populate the about version string builder.
766         aboutVersionStringBuilder.append(privacyBrowserTextView.text)
767         aboutVersionStringBuilder.append("\n")
768         aboutVersionStringBuilder.append(versionTextView.text)
769         aboutVersionStringBuilder.append("\n\n")
770         aboutVersionStringBuilder.append(hardwareTextView.text)
771         aboutVersionStringBuilder.append("\n")
772         aboutVersionStringBuilder.append(brandTextView.text)
773         aboutVersionStringBuilder.append("\n")
774         aboutVersionStringBuilder.append(manufacturerTextView.text)
775         aboutVersionStringBuilder.append("\n")
776         aboutVersionStringBuilder.append(modelTextView.text)
777         aboutVersionStringBuilder.append("\n")
778         aboutVersionStringBuilder.append(deviceTextView.text)
779         aboutVersionStringBuilder.append("\n")
780         aboutVersionStringBuilder.append(bootloaderTextView.text)
781         aboutVersionStringBuilder.append("\n")
782         if (radioTextView.visibility == View.VISIBLE) {
783             aboutVersionStringBuilder.append(radioTextView.text)
784             aboutVersionStringBuilder.append("\n")
785         }
786         aboutVersionStringBuilder.append("\n")
787         aboutVersionStringBuilder.append(softwareTextView.text)
788         aboutVersionStringBuilder.append("\n")
789         aboutVersionStringBuilder.append(androidTextView.text)
790         aboutVersionStringBuilder.append("\n")
791         if (securityPatchTextView.visibility == View.VISIBLE) {
792             aboutVersionStringBuilder.append(securityPatchTextView.text)
793             aboutVersionStringBuilder.append("\n")
794         }
795         aboutVersionStringBuilder.append(buildTextView.text)
796         aboutVersionStringBuilder.append("\n")
797         aboutVersionStringBuilder.append(kernelTextView.text)
798         aboutVersionStringBuilder.append("\n")
799         if (webViewProviderTextView.visibility == View.VISIBLE) {
800             aboutVersionStringBuilder.append(webViewProviderTextView.text)
801             aboutVersionStringBuilder.append("\n")
802         }
803         aboutVersionStringBuilder.append(webViewVersionTextView.text)
804         aboutVersionStringBuilder.append("\n")
805         if (orbotTextView.visibility == View.VISIBLE) {
806             aboutVersionStringBuilder.append(orbotTextView.text)
807             aboutVersionStringBuilder.append("\n")
808         }
809         if (i2pTextView.visibility == View.VISIBLE) {
810             aboutVersionStringBuilder.append(i2pTextView.text)
811             aboutVersionStringBuilder.append("\n")
812         }
813         if (openKeychainTextView.visibility == View.VISIBLE) {
814             aboutVersionStringBuilder.append(openKeychainTextView.text)
815             aboutVersionStringBuilder.append("\n")
816         }
817         aboutVersionStringBuilder.append("\n")
818         aboutVersionStringBuilder.append(memoryUsageTextView.text)
819         aboutVersionStringBuilder.append("\n")
820         aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
821         aboutVersionStringBuilder.append("\n")
822         aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
823         aboutVersionStringBuilder.append("\n")
824         aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
825         aboutVersionStringBuilder.append("\n")
826         aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
827         aboutVersionStringBuilder.append("\n")
828         aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
829         aboutVersionStringBuilder.append("\n")
830         aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
831         aboutVersionStringBuilder.append("\n")
832         aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
833         aboutVersionStringBuilder.append("\n\n")
834         aboutVersionStringBuilder.append(filterListsTextView.text)
835         aboutVersionStringBuilder.append("\n")
836         aboutVersionStringBuilder.append(easyListTextView.text)
837         aboutVersionStringBuilder.append("\n")
838         aboutVersionStringBuilder.append(easyPrivacyTextView.text)
839         aboutVersionStringBuilder.append("\n")
840         aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
841         aboutVersionStringBuilder.append("\n")
842         aboutVersionStringBuilder.append(fanboySocialTextView.text)
843         aboutVersionStringBuilder.append("\n")
844         aboutVersionStringBuilder.append(ultraListTextView.text)
845         aboutVersionStringBuilder.append("\n")
846         aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
847         aboutVersionStringBuilder.append("\n\n")
848         aboutVersionStringBuilder.append(packageSignatureTextView.text)
849         aboutVersionStringBuilder.append("\n")
850         aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
851         aboutVersionStringBuilder.append("\n")
852         aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
853         aboutVersionStringBuilder.append("\n")
854         aboutVersionStringBuilder.append(certificateStartDateTextView.text)
855         aboutVersionStringBuilder.append("\n")
856         aboutVersionStringBuilder.append(certificateEndDateTextView.text)
857         aboutVersionStringBuilder.append("\n")
858         aboutVersionStringBuilder.append(certificateVersionTextView.text)
859         aboutVersionStringBuilder.append("\n")
860         aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
861         aboutVersionStringBuilder.append("\n")
862         aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
863
864         // Return the string.
865         return aboutVersionStringBuilder.toString()
866     }
867 }