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