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