]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt
Add options to copy, share, and save View Headers. https://redmine.stoutner.com...
[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.webkit.WebView
45 import android.widget.TextView
46
47 import androidx.activity.result.contract.ActivityResultContracts
48 import androidx.fragment.app.Fragment
49 import androidx.webkit.WebViewCompat
50
51 import com.google.android.material.snackbar.Snackbar
52
53 import com.stoutner.privacybrowser.BuildConfig
54 import com.stoutner.privacybrowser.R
55 import com.stoutner.privacybrowser.coroutines.SaveAboutVersionImageCoroutine
56
57 import kotlinx.coroutines.CoroutineScope
58 import kotlinx.coroutines.Dispatchers
59 import kotlinx.coroutines.launch
60 import kotlinx.coroutines.withContext
61
62 import java.io.ByteArrayInputStream
63 import java.io.InputStream
64 import java.lang.Exception
65 import java.nio.charset.StandardCharsets
66 import java.security.cert.CertificateException
67 import java.security.cert.CertificateFactory
68 import java.security.cert.X509Certificate
69 import java.text.DateFormat
70 import java.text.NumberFormat
71
72 import kotlin.text.StringBuilder
73
74 // Define the class constants.
75 private const val FILTERLISTS_VERSIONS = "filterlists_versions"
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 buildLabel = getString(R.string.build)
285         val kernelLabel = getString(R.string.kernel)
286         val webViewVersionLabel = getString(R.string.webview_version)
287         appConsumedMemoryLabel = getString(R.string.app_consumed_memory)
288         appAvailableMemoryLabel = getString(R.string.app_available_memory)
289         appTotalMemoryLabel = getString(R.string.app_total_memory)
290         appMaximumMemoryLabel = getString(R.string.app_maximum_memory)
291         systemConsumedMemoryLabel = getString(R.string.system_consumed_memory)
292         systemAvailableMemoryLabel = getString(R.string.system_available_memory)
293         systemTotalMemoryLabel = getString(R.string.system_total_memory)
294         val easyListLabel = getString(R.string.easylist_label)
295         val easyPrivacyLabel = getString(R.string.easyprivacy_label)
296         val fanboyAnnoyanceLabel = getString(R.string.fanboys_annoyance_label)
297         val fanboySocialLabel = getString(R.string.fanboys_social_label)
298         val ultraListLabel = getString(R.string.ultralist_label)
299         val ultraPrivacyLabel = getString(R.string.ultraprivacy_label)
300         val issuerDNLabel = getString(R.string.issuer_dn)
301         val subjectDNLabel = getString(R.string.subject_dn)
302         val startDateLabel = getString(R.string.start_date)
303         val endDateLabel = getString(R.string.end_date)
304         val certificateVersionLabel = getString(R.string.certificate_version)
305         val serialNumberLabel = getString(R.string.serial_number)
306         val signatureAlgorithmLabel = getString(R.string.signature_algorithm)
307
308         // The WebView layout is only used to get the default user agent from `bare_webview`.  It is not used to render content on the screen.
309         // Once the minimum API >= 26 this can be accomplished with the WebView package info.
310         val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false)
311         val tabLayoutWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
312         val userAgentString = tabLayoutWebView.settings.userAgentString
313
314         // Get the device's information and store it in strings.
315         val brand = Build.BRAND
316         val manufacturer = Build.MANUFACTURER
317         val model = Build.MODEL
318         val device = Build.DEVICE
319         val bootloader = Build.BOOTLOADER
320         val radio = Build.getRadioVersion()
321         val android = getString(R.string.api, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)
322         val build = Build.DISPLAY
323         val kernel = System.getProperty("os.version")
324
325         // Get the WebView version, selecting the substring that begins after `Chrome/` and goes until the next ` `.
326         val webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")))
327
328         // Get the Orbot version name if Orbot is installed.
329         val orbot: String = try {
330             // Store the version name.
331             requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
332         } catch (exception: PackageManager.NameNotFoundException) {  // Orbot is not installed.
333             // Store an empty string.
334             ""
335         }
336
337         // Get the I2P version name if I2P is installed.
338         val i2p: String = try {
339             // Check to see if the F-Droid flavor is installed.
340             requireContext().getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
341         } catch (exception: PackageManager.NameNotFoundException) {  // The F-Droid flavor is not installed.
342             try {
343                 // Check to see if the F-Droid flavor is installed.
344                 requireContext().getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
345             } catch (exception: PackageManager.NameNotFoundException) {  // The Google Play flavor is not installed either.
346                 // Store an empty string.
347                 ""
348             }
349         }
350
351         // Get the OpenKeychain version name if it is installed.
352         val openKeychain: String = try {
353             // Store the version name.
354             requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
355         } catch (exception: PackageManager.NameNotFoundException) {  // OpenKeychain is not installed.
356             // Store an empty string.
357             ""
358         }
359
360         // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
361         val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
362         val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
363         val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
364         val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
365         val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
366         val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
367         val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
368         val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
369         val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
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         buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
388         kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389         webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390         easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
391         easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392         fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
393         fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
394         ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
395         ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
396
397         // Display the strings in the text boxes.
398         versionTextView.text = version
399         brandTextView.text = brandStringBuilder
400         manufacturerTextView.text = manufacturerStringBuilder
401         modelTextView.text = modelStringBuilder
402         deviceTextView.text = deviceStringBuilder
403         bootloaderTextView.text = bootloaderStringBuilder
404         androidTextView.text = androidStringBuilder
405         buildTextView.text = buildStringBuilder
406         kernelTextView.text = kernelStringBuilder
407         webViewVersionTextView.text = webViewVersionStringBuilder
408         easyListTextView.text = easyListStringBuilder
409         easyPrivacyTextView.text = easyPrivacyStringBuilder
410         fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
411         fanboySocialTextView.text = fanboySocialStringBuilder
412         ultraListTextView.text = ultraListStringBuilder
413         ultraPrivacyTextView.text = ultraPrivacyStringBuilder
414
415         // Only populate the radio text view if there is a radio in the device.
416         // 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>
417         if (radio != null && radio.isNotEmpty()) {
418             // Setup the label.
419             val radioLabel = getString(R.string.radio)
420
421             // Create a spannable string builder.
422             val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
423
424             // Set the span to display the radio in blue.
425             radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
426
427             // Display the string in the text view.
428             radioTextView.text = radioStringBuilder
429         } else {  // This device does not have a radio.
430             // Hide the radio text view.
431             radioTextView.visibility = View.GONE
432         }
433
434         // Setup the label.
435         val securityPatchLabel = getString(R.string.security_patch)
436
437         // Get the security patch version.
438         val securityPatch = Build.VERSION.SECURITY_PATCH
439
440         // Create a spannable string builder.
441         val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
442
443         // Set the span to display the security patch version in blue.
444         securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
445
446         // Display the string in the text view.
447         securityPatchTextView.text = securityPatchStringBuilder
448
449         // Create the WebView provider label.
450         val webViewProviderLabel = getString(R.string.webview_provider)
451
452         // Get the current WebView package info.
453         val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
454
455         // Get the WebView provider name.
456         val webViewPackageName = webViewPackageInfo.packageName
457
458         // Create the spannable string builder.
459         val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
460
461         // Apply the coloration.
462         webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
463
464         // Display the WebView provider.
465         webViewProviderTextView.text = webViewProviderStringBuilder
466
467         // Only populate the Orbot text view if it is installed.
468         if (orbot.isNotEmpty()) {
469             // Setup the label.
470             val orbotLabel = getString(R.string.orbot)
471
472             // Create a spannable string builder.
473             val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
474
475             // Set the span to display the Orbot version.
476             orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
477
478             // Display the string in the text view.
479             orbotTextView.text = orbotStringBuilder
480         } else {  // Orbot is not installed.
481             // Hide the Orbot text view.
482             orbotTextView.visibility = View.GONE
483         }
484
485         // Only populate the I2P text view if it is installed.
486         if (i2p.isNotEmpty()) {
487             // Setup the label.
488             val i2pLabel = getString(R.string.i2p)
489
490             // Create a spannable string builder.
491             val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
492
493             // Set the span to display the I2P version.
494             i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
495
496             // Display the string in the text view.
497             i2pTextView.text = i2pStringBuilder
498         } else {  // I2P is not installed.
499             // Hide the I2P text view.
500             i2pTextView.visibility = View.GONE
501         }
502
503         // Only populate the OpenKeychain text view if it is installed.
504         if (openKeychain.isNotEmpty()) {
505             // Setup the label.
506             val openKeychainLabel = getString(R.string.openkeychain)
507
508             // Create a spannable string builder.
509             val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
510
511             // Set the span to display the OpenKeychain version.
512             openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
513
514             // Display the string in the text view.
515             openKeychainTextView.text = openKeychainStringBuilder
516         } else {  //OpenKeychain is not installed.
517             // Hide the OpenKeychain text view.
518             openKeychainTextView.visibility = View.GONE
519         }
520
521         // Display the package signature.
522         try {
523             // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
524             // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead.  Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
525             @Suppress("DEPRECATION")
526             @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
527                 .signatures[0]
528
529             // Convert the signature to a byte array input stream.
530             val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
531
532             // Display the certificate information on the screen.
533             try {
534                 // Instantiate a certificate factory.
535                 val certificateFactory = CertificateFactory.getInstance("X509")
536
537                 // Generate an X509 certificate.
538                 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
539
540                 // Store the individual sections of the certificate.
541                 val issuerDNPrincipal = x509Certificate.issuerDN
542                 val subjectDNPrincipal = x509Certificate.subjectDN
543                 val startDate = x509Certificate.notBefore
544                 val endDate = x509Certificate.notAfter
545                 val certificateVersion = x509Certificate.version
546                 val serialNumberBigInteger = x509Certificate.serialNumber
547                 val signatureAlgorithmNameString = x509Certificate.sigAlgName
548
549                 // Create a spannable string builder for each text view that needs multiple colors of text.
550                 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
551                 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
552                 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
553                 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
554                 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
555                 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
556                 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
557
558                 // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
559                 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
560                 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
561                 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
562                 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
563                 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
564                 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
565                 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
566
567                 // Display the strings in the text boxes.
568                 certificateIssuerDnTextView.text = issuerDNStringBuilder
569                 certificateSubjectDnTextView.text = subjectDNStringBuilder
570                 certificateStartDateTextView.text = startDateStringBuilder
571                 certificateEndDateTextView.text = endDataStringBuilder
572                 certificateVersionTextView.text = certificateVersionStringBuilder
573                 certificateSerialNumberTextView.text = serialNumberStringBuilder
574                 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
575             } catch (certificateException: CertificateException) {
576                 // Do nothing if there is a certificate error.
577             }
578
579             // Get a handle for the runtime.
580             runtime = Runtime.getRuntime()
581
582             // Get a handle for the activity manager.
583             activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
584
585             // Instantiate a memory info variable.
586             memoryInfo = ActivityManager.MemoryInfo()
587
588             // Define a number format.
589             numberFormat = NumberFormat.getInstance()
590
591             // Set the minimum and maximum number of fraction digits.
592             numberFormat.minimumFractionDigits = 2
593             numberFormat.maximumFractionDigits = 2
594
595             // Update the memory usage.
596             updateMemoryUsage(requireActivity())
597         } catch (e: PackageManager.NameNotFoundException) {
598             // Do nothing if the package manager says Privacy Browser isn't installed.
599         }
600
601         // Scroll the tab if the saved instance state is not null.
602         if (savedInstanceState != null) {
603             aboutVersionLayout.post {
604                 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
605                 aboutVersionLayout.scrollY = savedInstanceState.getInt("scroll_y")
606             }
607         }
608
609         // Return the tab layout.
610         return aboutVersionLayout
611     }
612
613     override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
614         // Inflate the about version menu.
615         menuInflater.inflate(R.menu.about_version_options_menu, menu)
616
617         // Run the default commands.
618         super.onCreateOptionsMenu(menu, menuInflater)
619     }
620
621     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
622         // Run the appropriate commands.
623         when (menuItem.itemId) {
624             R.id.copy -> {  // Copy.
625                 // Get the about version string.
626                 val aboutVersionString = getAboutVersionString()
627
628                 // Get a handle for the clipboard manager.
629                 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
630
631                 // Place the about version string in a clip data.
632                 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
633
634                 // Place the clip data on the clipboard.
635                 clipboardManager.setPrimaryClip(aboutVersionClipData)
636
637                 // Display a snackbar if the API <= 32 (Android 12L).  Beginning in Android 13 the OS displays a notification that covers up the snackbar.
638                 if (Build.VERSION.SDK_INT <= 32)
639                     Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
640
641                 // Consume the event.
642                 return true
643             }
644
645             R.id.share -> {  // Share.
646                 // Get the about version string.
647                 val aboutString = getAboutVersionString()
648
649                 // Create a share intent.
650                 val shareIntent = Intent(Intent.ACTION_SEND)
651
652                 // Add the about version string to the intent.
653                 shareIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
654
655                 // Set the MIME type.
656                 shareIntent.type = "text/plain"
657
658                 // Set the intent to open in a new task.
659                 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
660
661                 // Make it so.
662                 startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
663
664                 // Consume the event.
665                 return true
666             }
667
668             R.id.save_text -> {  // Save text.
669                 // Open the file picker.
670                 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
671
672                 // Consume the event.
673                 return true
674             }
675
676             R.id.save_image -> {  // Save image.
677                 // Open the file picker.
678                 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
679
680                 // Consume the event.
681                 return true
682             }
683
684             else -> {  // The home button was selected.
685                 // Run the parents class on return.
686                 return super.onOptionsItemSelected(menuItem)
687             }
688         }
689     }
690
691     override fun onSaveInstanceState(savedInstanceState: Bundle) {
692         // Run the default commands.
693         super.onSaveInstanceState(savedInstanceState)
694
695         // Save the scroll positions.
696         savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
697         savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
698     }
699
700     override fun onPause() {
701         // Run the default commands.
702         super.onPause()
703
704         // Pause the updating of the memory usage.
705         updateMemoryUsageBoolean = false
706     }
707
708     override fun onResume() {
709         // Run the default commands.
710         super.onResume()
711
712         // Resume the updating of the memory usage.
713         updateMemoryUsageBoolean = true
714     }
715
716     private fun updateMemoryUsage(activity: Activity) {
717         try {
718             // Update the memory usage if enabled.
719             if (updateMemoryUsageBoolean) {
720                 // Populate the memory info variable.
721                 activityManager.getMemoryInfo(memoryInfo)
722
723                 // Get the app memory information.
724                 val appAvailableMemoryLong = runtime.freeMemory()
725                 val appTotalMemoryLong = runtime.totalMemory()
726                 val appMaximumMemoryLong = runtime.maxMemory()
727
728                 // Calculate the app consumed memory.
729                 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
730
731                 // Get the system memory information.
732                 val systemTotalMemoryLong = memoryInfo.totalMem
733                 val systemAvailableMemoryLong = memoryInfo.availMem
734
735                 // Calculate the system consumed memory.
736                 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
737
738                 // Convert the memory information into mebibytes.
739                 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
740                 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
741                 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
742                 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
743                 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
744                 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
745                 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
746
747                 // Get the mebibyte string.
748                 val mebibyte = getString(R.string.mebibyte)
749
750                 // Calculate the mebibyte length.
751                 val mebibyteLength = mebibyte.length
752
753                 // Create spannable string builders.
754                 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
755                 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
756                 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
757                 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
758                 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
759                 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
760                 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
761
762                 // Setup the spans to display the memory information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
763                 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
764                 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
765                 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
766                 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
767                 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
768                 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
769                 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
770
771                 // Display the string in the text boxes.
772                 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
773                 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
774                 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
775                 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
776                 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
777                 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
778                 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
779             }
780
781             // Schedule another memory update if the activity has not been destroyed.
782             if (!activity.isDestroyed) {
783                 // Create a handler to update the memory usage.
784                 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
785
786                 // Create a runnable to update the memory usage.
787                 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
788
789                 // Update the memory usage after 1000 milliseconds
790                 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
791             }
792         } catch (exception: Exception) {
793             // Do nothing.
794         }
795     }
796
797     private fun getAboutVersionString(): String {
798         // Initialize an about version string builder.
799         val aboutVersionStringBuilder = StringBuilder()
800
801         // Populate the about version string builder.
802         aboutVersionStringBuilder.append(privacyBrowserTextView.text)
803         aboutVersionStringBuilder.append("\n")
804         aboutVersionStringBuilder.append(versionTextView.text)
805         aboutVersionStringBuilder.append("\n\n")
806         aboutVersionStringBuilder.append(hardwareTextView.text)
807         aboutVersionStringBuilder.append("\n")
808         aboutVersionStringBuilder.append(brandTextView.text)
809         aboutVersionStringBuilder.append("\n")
810         aboutVersionStringBuilder.append(manufacturerTextView.text)
811         aboutVersionStringBuilder.append("\n")
812         aboutVersionStringBuilder.append(modelTextView.text)
813         aboutVersionStringBuilder.append("\n")
814         aboutVersionStringBuilder.append(deviceTextView.text)
815         aboutVersionStringBuilder.append("\n")
816         aboutVersionStringBuilder.append(bootloaderTextView.text)
817         aboutVersionStringBuilder.append("\n")
818         if (radioTextView.visibility == View.VISIBLE) {
819             aboutVersionStringBuilder.append(radioTextView.text)
820             aboutVersionStringBuilder.append("\n")
821         }
822         aboutVersionStringBuilder.append("\n")
823         aboutVersionStringBuilder.append(softwareTextView.text)
824         aboutVersionStringBuilder.append("\n")
825         aboutVersionStringBuilder.append(androidTextView.text)
826         aboutVersionStringBuilder.append("\n")
827         if (securityPatchTextView.visibility == View.VISIBLE) {
828             aboutVersionStringBuilder.append(securityPatchTextView.text)
829             aboutVersionStringBuilder.append("\n")
830         }
831         aboutVersionStringBuilder.append(buildTextView.text)
832         aboutVersionStringBuilder.append("\n")
833         aboutVersionStringBuilder.append(kernelTextView.text)
834         aboutVersionStringBuilder.append("\n")
835         if (webViewProviderTextView.visibility == View.VISIBLE) {
836             aboutVersionStringBuilder.append(webViewProviderTextView.text)
837             aboutVersionStringBuilder.append("\n")
838         }
839         aboutVersionStringBuilder.append(webViewVersionTextView.text)
840         aboutVersionStringBuilder.append("\n")
841         if (orbotTextView.visibility == View.VISIBLE) {
842             aboutVersionStringBuilder.append(orbotTextView.text)
843             aboutVersionStringBuilder.append("\n")
844         }
845         if (i2pTextView.visibility == View.VISIBLE) {
846             aboutVersionStringBuilder.append(i2pTextView.text)
847             aboutVersionStringBuilder.append("\n")
848         }
849         if (openKeychainTextView.visibility == View.VISIBLE) {
850             aboutVersionStringBuilder.append(openKeychainTextView.text)
851             aboutVersionStringBuilder.append("\n")
852         }
853         aboutVersionStringBuilder.append("\n")
854         aboutVersionStringBuilder.append(memoryUsageTextView.text)
855         aboutVersionStringBuilder.append("\n")
856         aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
857         aboutVersionStringBuilder.append("\n")
858         aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
859         aboutVersionStringBuilder.append("\n")
860         aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
861         aboutVersionStringBuilder.append("\n")
862         aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
863         aboutVersionStringBuilder.append("\n")
864         aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
865         aboutVersionStringBuilder.append("\n")
866         aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
867         aboutVersionStringBuilder.append("\n")
868         aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
869         aboutVersionStringBuilder.append("\n\n")
870         aboutVersionStringBuilder.append(filterListsTextView.text)
871         aboutVersionStringBuilder.append("\n")
872         aboutVersionStringBuilder.append(easyListTextView.text)
873         aboutVersionStringBuilder.append("\n")
874         aboutVersionStringBuilder.append(easyPrivacyTextView.text)
875         aboutVersionStringBuilder.append("\n")
876         aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
877         aboutVersionStringBuilder.append("\n")
878         aboutVersionStringBuilder.append(fanboySocialTextView.text)
879         aboutVersionStringBuilder.append("\n")
880         aboutVersionStringBuilder.append(ultraListTextView.text)
881         aboutVersionStringBuilder.append("\n")
882         aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
883         aboutVersionStringBuilder.append("\n\n")
884         aboutVersionStringBuilder.append(packageSignatureTextView.text)
885         aboutVersionStringBuilder.append("\n")
886         aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
887         aboutVersionStringBuilder.append("\n")
888         aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
889         aboutVersionStringBuilder.append("\n")
890         aboutVersionStringBuilder.append(certificateStartDateTextView.text)
891         aboutVersionStringBuilder.append("\n")
892         aboutVersionStringBuilder.append(certificateEndDateTextView.text)
893         aboutVersionStringBuilder.append("\n")
894         aboutVersionStringBuilder.append(certificateVersionTextView.text)
895         aboutVersionStringBuilder.append("\n")
896         aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
897         aboutVersionStringBuilder.append("\n")
898         aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
899
900         // Return the string.
901         return aboutVersionStringBuilder.toString()
902     }
903 }