]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt
Migrate the remaining classes to Kotlin. https://redmine.stoutner.com/issues/989
[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 BLOCKLIST_VERSIONS = "blocklist_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 blocklistVersions: Array<String>
84     private lateinit var aboutVersionLayout: View
85     private lateinit var appConsumedMemoryLabel: String
86     private lateinit var appAvailableMemoryLabel: String
87     private lateinit var appTotalMemoryLabel: String
88     private lateinit var appMaximumMemoryLabel: String
89     private lateinit var systemConsumedMemoryLabel: String
90     private lateinit var systemAvailableMemoryLabel: String
91     private lateinit var systemTotalMemoryLabel: String
92     private lateinit var runtime: Runtime
93     private lateinit var activityManager: ActivityManager
94     private lateinit var memoryInfo: ActivityManager.MemoryInfo
95     private lateinit var numberFormat: NumberFormat
96     private lateinit var blueColorSpan: ForegroundColorSpan
97
98     // Declare the class views.
99     private lateinit var privacyBrowserTextView: TextView
100     private lateinit var versionTextView: TextView
101     private lateinit var hardwareTextView: TextView
102     private lateinit var brandTextView: TextView
103     private lateinit var manufacturerTextView: TextView
104     private lateinit var modelTextView: TextView
105     private lateinit var deviceTextView: TextView
106     private lateinit var bootloaderTextView: TextView
107     private lateinit var radioTextView: TextView
108     private lateinit var softwareTextView: TextView
109     private lateinit var androidTextView: TextView
110     private lateinit var securityPatchTextView: TextView
111     private lateinit var buildTextView: TextView
112     private lateinit var kernelTextView: TextView
113     private lateinit var webViewProviderTextView: TextView
114     private lateinit var webViewVersionTextView: TextView
115     private lateinit var orbotTextView: TextView
116     private lateinit var i2pTextView: TextView
117     private lateinit var openKeychainTextView: TextView
118     private lateinit var memoryUsageTextView: TextView
119     private lateinit var appConsumedMemoryTextView: TextView
120     private lateinit var appAvailableMemoryTextView: TextView
121     private lateinit var appTotalMemoryTextView: TextView
122     private lateinit var appMaximumMemoryTextView: TextView
123     private lateinit var systemConsumedMemoryTextView: TextView
124     private lateinit var systemAvailableMemoryTextView: TextView
125     private lateinit var systemTotalMemoryTextView: TextView
126     private lateinit var blocklistsTextView: TextView
127     private lateinit var easyListTextView: TextView
128     private lateinit var easyPrivacyTextView: TextView
129     private lateinit var fanboyAnnoyanceTextView: TextView
130     private lateinit var fanboySocialTextView: TextView
131     private lateinit var ultraListTextView: TextView
132     private lateinit var ultraPrivacyTextView: TextView
133     private lateinit var packageSignatureTextView: TextView
134     private lateinit var certificateIssuerDnTextView: TextView
135     private lateinit var certificateSubjectDnTextView: TextView
136     private lateinit var certificateStartDateTextView: TextView
137     private lateinit var certificateEndDateTextView: TextView
138     private lateinit var certificateVersionTextView: TextView
139     private lateinit var certificateSerialNumberTextView: TextView
140     private lateinit var certificateSignatureAlgorithmTextView: TextView
141
142     companion object {
143         fun createTab(blocklistVersions: Array<String>): AboutVersionFragment {
144             // Create an arguments bundle.
145             val argumentsBundle = Bundle()
146
147             // Store the arguments in the bundle.
148             argumentsBundle.putStringArray(BLOCKLIST_VERSIONS, blocklistVersions)
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         blocklistVersions = requireArguments().getStringArray(BLOCKLIST_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         blocklistsTextView = aboutVersionLayout.findViewById(R.id.blocklists)
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.  The newer `getPackageInfo()` may be used once the minimum API >= 33.
331             @Suppress("DEPRECATION")
332             requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
333         } catch (exception: PackageManager.NameNotFoundException) {  // Orbot is not installed.
334             // Store an empty string.
335             ""
336         }
337
338         // Get the I2P version name if I2P is installed.
339         val i2p: String = try {
340             // Check to see if the F-Droid flavor is installed.  The newer `getPackageInfo()` may be used once the minimum API >= 33.
341             @Suppress("DEPRECATION")
342             requireContext().getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
343         } catch (exception: PackageManager.NameNotFoundException) {  // The F-Droid flavor is not installed.
344             try {
345                 // Check to see if the F-Droid flavor is installed.  The newer `getPackageInfo()` may be used once the minimum API >= 33.
346                 @Suppress("DEPRECATION")
347                 requireContext().getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
348             } catch (exception: PackageManager.NameNotFoundException) {  // The Google Play flavor is not installed either.
349                 // Store an empty string.
350                 ""
351             }
352         }
353
354         // Get the OpenKeychain version name if it is installed.
355         val openKeychain: String = try {
356             // Store the version name.  The newer `getPackageInfo()` may be used once the minimum API >= 33.
357             @Suppress("DEPRECATION")
358             requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
359         } catch (exception: PackageManager.NameNotFoundException) {  // OpenKeychain is not installed.
360             // Store an empty string.
361             ""
362         }
363
364         // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
365         val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
366         val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
367         val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
368         val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
369         val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
370         val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
371         val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
372         val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
373         val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
374         val easyListStringBuilder = SpannableStringBuilder(easyListLabel + blocklistVersions[0])
375         val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1])
376         val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2])
377         val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3])
378         val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + blocklistVersions[4])
379         val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5])
380
381         // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until the minimum API >= 23.
382         blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
383
384         // Set the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
385         brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
386         manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
387         modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
388         deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389         bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390         androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
391         buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392         kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
393         webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
394         easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
395         easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
396         fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
397         fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
398         ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
399         ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
400
401         // Display the strings in the text boxes.
402         versionTextView.text = version
403         brandTextView.text = brandStringBuilder
404         manufacturerTextView.text = manufacturerStringBuilder
405         modelTextView.text = modelStringBuilder
406         deviceTextView.text = deviceStringBuilder
407         bootloaderTextView.text = bootloaderStringBuilder
408         androidTextView.text = androidStringBuilder
409         buildTextView.text = buildStringBuilder
410         kernelTextView.text = kernelStringBuilder
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         // Setup the label.
439         val securityPatchLabel = getString(R.string.security_patch)
440
441         // Get the security patch version.
442         val securityPatch = Build.VERSION.SECURITY_PATCH
443
444         // Create a spannable string builder.
445         val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
446
447         // Set the span to display the security patch version in blue.
448         securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
449
450         // Display the string in the text view.
451         securityPatchTextView.text = securityPatchStringBuilder
452
453         // Create the WebView provider label.
454         val webViewProviderLabel = getString(R.string.webview_provider)
455
456         // Get the current WebView package info.
457         val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
458
459         // Get the WebView provider name.
460         val webViewPackageName = webViewPackageInfo.packageName
461
462         // Create the spannable string builder.
463         val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
464
465         // Apply the coloration.
466         webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
467
468         // Display the WebView provider.
469         webViewProviderTextView.text = webViewProviderStringBuilder
470
471         // Only populate the Orbot text view if it is installed.
472         if (orbot.isNotEmpty()) {
473             // Setup the label.
474             val orbotLabel = getString(R.string.orbot)
475
476             // Create a spannable string builder.
477             val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
478
479             // Set the span to display the Orbot version.
480             orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
481
482             // Display the string in the text view.
483             orbotTextView.text = orbotStringBuilder
484         } else {  // Orbot is not installed.
485             // Hide the Orbot text view.
486             orbotTextView.visibility = View.GONE
487         }
488
489         // Only populate the I2P text view if it is installed.
490         if (i2p.isNotEmpty()) {
491             // Setup the label.
492             val i2pLabel = getString(R.string.i2p)
493
494             // Create a spannable string builder.
495             val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
496
497             // Set the span to display the I2P version.
498             i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
499
500             // Display the string in the text view.
501             i2pTextView.text = i2pStringBuilder
502         } else {  // I2P is not installed.
503             // Hide the I2P text view.
504             i2pTextView.visibility = View.GONE
505         }
506
507         // Only populate the OpenKeychain text view if it is installed.
508         if (openKeychain.isNotEmpty()) {
509             // Setup the label.
510             val openKeychainLabel = getString(R.string.openkeychain)
511
512             // Create a spannable string builder.
513             val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
514
515             // Set the span to display the OpenKeychain version.
516             openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
517
518             // Display the string in the text view.
519             openKeychainTextView.text = openKeychainStringBuilder
520         } else {  //OpenKeychain is not installed.
521             // Hide the OpenKeychain text view.
522             openKeychainTextView.visibility = View.GONE
523         }
524
525         // Display the package signature.
526         try {
527             // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
528             // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead.  Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
529             @Suppress("DEPRECATION")
530             @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
531                 .signatures[0]
532
533             // Convert the signature to a byte array input stream.
534             val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
535
536             // Display the certificate information on the screen.
537             try {
538                 // Instantiate a certificate factory.
539                 val certificateFactory = CertificateFactory.getInstance("X509")
540
541                 // Generate an X509 certificate.
542                 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
543
544                 // Store the individual sections of the certificate.
545                 val issuerDNPrincipal = x509Certificate.issuerDN
546                 val subjectDNPrincipal = x509Certificate.subjectDN
547                 val startDate = x509Certificate.notBefore
548                 val endDate = x509Certificate.notAfter
549                 val certificateVersion = x509Certificate.version
550                 val serialNumberBigInteger = x509Certificate.serialNumber
551                 val signatureAlgorithmNameString = x509Certificate.sigAlgName
552
553                 // Create a spannable string builder for each text view that needs multiple colors of text.
554                 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
555                 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
556                 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
557                 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
558                 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
559                 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
560                 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
561
562                 // Setup the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
563                 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
564                 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
565                 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
566                 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
567                 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
568                 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
569                 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
570
571                 // Display the strings in the text boxes.
572                 certificateIssuerDnTextView.text = issuerDNStringBuilder
573                 certificateSubjectDnTextView.text = subjectDNStringBuilder
574                 certificateStartDateTextView.text = startDateStringBuilder
575                 certificateEndDateTextView.text = endDataStringBuilder
576                 certificateVersionTextView.text = certificateVersionStringBuilder
577                 certificateSerialNumberTextView.text = serialNumberStringBuilder
578                 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
579             } catch (certificateException: CertificateException) {
580                 // Do nothing if there is a certificate error.
581             }
582
583             // Get a handle for the runtime.
584             runtime = Runtime.getRuntime()
585
586             // Get a handle for the activity manager.
587             activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
588
589             // Instantiate a memory info variable.
590             memoryInfo = ActivityManager.MemoryInfo()
591
592             // Define a number format.
593             numberFormat = NumberFormat.getInstance()
594
595             // Set the minimum and maximum number of fraction digits.
596             numberFormat.minimumFractionDigits = 2
597             numberFormat.maximumFractionDigits = 2
598
599             // Update the memory usage.
600             updateMemoryUsage(requireActivity())
601         } catch (e: PackageManager.NameNotFoundException) {
602             // Do nothing if the package manager says Privacy Browser isn't installed.
603         }
604
605         // Scroll the tab if the saved instance state is not null.
606         if (savedInstanceState != null) {
607             aboutVersionLayout.post {
608                 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
609                 aboutVersionLayout.scrollY = savedInstanceState.getInt("scroll_y")
610             }
611         }
612
613         // Return the tab layout.
614         return aboutVersionLayout
615     }
616
617     override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
618         // Inflate the about version menu.
619         menuInflater.inflate(R.menu.about_version_options_menu, menu)
620
621         // Run the default commands.
622         super.onCreateOptionsMenu(menu, menuInflater)
623     }
624
625     override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
626         // Run the appropriate commands.
627         when (menuItem.itemId) {
628             R.id.copy -> {  // Copy.
629                 // Get the about version string.
630                 val aboutVersionString = getAboutVersionString()
631
632                 // Get a handle for the clipboard manager.
633                 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
634
635                 // Save the about version string in a clip data.
636                 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
637
638                 // Place the clip data on the clipboard.
639                 clipboardManager.setPrimaryClip(aboutVersionClipData)
640
641                 // Display a snackbar.
642                 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
643
644                 // Consume the event.
645                 return true
646             }
647
648             R.id.share -> {  // Share.
649                 // Get the about version string.
650                 val aboutString = getAboutVersionString()
651
652                 // Create an email intent.
653                 val emailIntent = Intent(Intent.ACTION_SEND)
654
655                 // Add the about version string to the intent.
656                 emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
657
658                 // Set the MIME type.
659                 emailIntent.type = "text/plain"
660
661                 // Set the intent to open in a new task.
662                 emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
663
664                 // Make it so.
665                 startActivity(Intent.createChooser(emailIntent, getString(R.string.share)))
666
667                 // Consume the event.
668                 return true
669             }
670
671             R.id.save_text -> {  // Save text.
672                 // Open the file picker.
673                 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
674
675                 // Consume the event.
676                 return true
677             }
678
679             R.id.save_image -> {  // Save image.
680                 // Open the file picker.
681                 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
682
683                 // Consume the event.
684                 return true
685             }
686             else -> {  // The home button was selected.
687                 // Run the parents class on return.
688                 return super.onOptionsItemSelected(menuItem)
689             }
690         }
691     }
692
693     override fun onSaveInstanceState(savedInstanceState: Bundle) {
694         // Run the default commands.
695         super.onSaveInstanceState(savedInstanceState)
696
697         // Save the scroll positions.
698         savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
699         savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
700     }
701
702     override fun onPause() {
703         // Run the default commands.
704         super.onPause()
705
706         // Pause the updating of the memory usage.
707         updateMemoryUsageBoolean = false
708     }
709
710     override fun onResume() {
711         // Run the default commands.
712         super.onResume()
713
714         // Resume the updating of the memory usage.
715         updateMemoryUsageBoolean = true
716     }
717
718     private fun updateMemoryUsage(activity: Activity) {
719         try {
720             // Update the memory usage if enabled.
721             if (updateMemoryUsageBoolean) {
722                 // Populate the memory info variable.
723                 activityManager.getMemoryInfo(memoryInfo)
724
725                 // Get the app memory information.
726                 val appAvailableMemoryLong = runtime.freeMemory()
727                 val appTotalMemoryLong = runtime.totalMemory()
728                 val appMaximumMemoryLong = runtime.maxMemory()
729
730                 // Calculate the app consumed memory.
731                 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
732
733                 // Get the system memory information.
734                 val systemTotalMemoryLong = memoryInfo.totalMem
735                 val systemAvailableMemoryLong = memoryInfo.availMem
736
737                 // Calculate the system consumed memory.
738                 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
739
740                 // Convert the memory information into mebibytes.
741                 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
742                 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
743                 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
744                 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
745                 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
746                 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
747                 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
748
749                 // Get the mebibyte string.
750                 val mebibyte = getString(R.string.mebibyte)
751
752                 // Calculate the mebibyte length.
753                 val mebibyteLength = mebibyte.length
754
755                 // Create spannable string builders.
756                 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
757                 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
758                 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
759                 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
760                 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
761                 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
762                 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
763
764                 // Setup the spans to display the memory information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
765                 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
766                 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
767                 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
768                 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
769                 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
770                 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
771                 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
772
773                 // Display the string in the text boxes.
774                 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
775                 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
776                 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
777                 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
778                 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
779                 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
780                 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
781             }
782
783             // Schedule another memory update if the activity has not been destroyed.
784             if (!activity.isDestroyed) {
785                 // Create a handler to update the memory usage.
786                 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
787
788                 // Create a runnable to update the memory usage.
789                 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
790
791                 // Update the memory usage after 1000 milliseconds
792                 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
793             }
794         } catch (exception: Exception) {
795             // Do nothing.
796         }
797     }
798
799     private fun getAboutVersionString(): String {
800         // Initialize an about version string builder.
801         val aboutVersionStringBuilder = StringBuilder()
802
803         // Populate the about version string builder.
804         aboutVersionStringBuilder.append(privacyBrowserTextView.text)
805         aboutVersionStringBuilder.append("\n")
806         aboutVersionStringBuilder.append(versionTextView.text)
807         aboutVersionStringBuilder.append("\n\n")
808         aboutVersionStringBuilder.append(hardwareTextView.text)
809         aboutVersionStringBuilder.append("\n")
810         aboutVersionStringBuilder.append(brandTextView.text)
811         aboutVersionStringBuilder.append("\n")
812         aboutVersionStringBuilder.append(manufacturerTextView.text)
813         aboutVersionStringBuilder.append("\n")
814         aboutVersionStringBuilder.append(modelTextView.text)
815         aboutVersionStringBuilder.append("\n")
816         aboutVersionStringBuilder.append(deviceTextView.text)
817         aboutVersionStringBuilder.append("\n")
818         aboutVersionStringBuilder.append(bootloaderTextView.text)
819         aboutVersionStringBuilder.append("\n")
820         if (radioTextView.visibility == View.VISIBLE) {
821             aboutVersionStringBuilder.append(radioTextView.text)
822             aboutVersionStringBuilder.append("\n")
823         }
824         aboutVersionStringBuilder.append("\n")
825         aboutVersionStringBuilder.append(softwareTextView.text)
826         aboutVersionStringBuilder.append("\n")
827         aboutVersionStringBuilder.append(androidTextView.text)
828         aboutVersionStringBuilder.append("\n")
829         if (securityPatchTextView.visibility == View.VISIBLE) {
830             aboutVersionStringBuilder.append(securityPatchTextView.text)
831             aboutVersionStringBuilder.append("\n")
832         }
833         aboutVersionStringBuilder.append(buildTextView.text)
834         aboutVersionStringBuilder.append("\n")
835         aboutVersionStringBuilder.append(kernelTextView.text)
836         aboutVersionStringBuilder.append("\n")
837         if (webViewProviderTextView.visibility == View.VISIBLE) {
838             aboutVersionStringBuilder.append(webViewProviderTextView.text)
839             aboutVersionStringBuilder.append("\n")
840         }
841         aboutVersionStringBuilder.append(webViewVersionTextView.text)
842         aboutVersionStringBuilder.append("\n")
843         if (orbotTextView.visibility == View.VISIBLE) {
844             aboutVersionStringBuilder.append(orbotTextView.text)
845             aboutVersionStringBuilder.append("\n")
846         }
847         if (i2pTextView.visibility == View.VISIBLE) {
848             aboutVersionStringBuilder.append(i2pTextView.text)
849             aboutVersionStringBuilder.append("\n")
850         }
851         if (openKeychainTextView.visibility == View.VISIBLE) {
852             aboutVersionStringBuilder.append(openKeychainTextView.text)
853             aboutVersionStringBuilder.append("\n")
854         }
855         aboutVersionStringBuilder.append("\n")
856         aboutVersionStringBuilder.append(memoryUsageTextView.text)
857         aboutVersionStringBuilder.append("\n")
858         aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
859         aboutVersionStringBuilder.append("\n")
860         aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
861         aboutVersionStringBuilder.append("\n")
862         aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
863         aboutVersionStringBuilder.append("\n")
864         aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
865         aboutVersionStringBuilder.append("\n")
866         aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
867         aboutVersionStringBuilder.append("\n")
868         aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
869         aboutVersionStringBuilder.append("\n")
870         aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
871         aboutVersionStringBuilder.append("\n\n")
872         aboutVersionStringBuilder.append(blocklistsTextView.text)
873         aboutVersionStringBuilder.append("\n")
874         aboutVersionStringBuilder.append(easyListTextView.text)
875         aboutVersionStringBuilder.append("\n")
876         aboutVersionStringBuilder.append(easyPrivacyTextView.text)
877         aboutVersionStringBuilder.append("\n")
878         aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
879         aboutVersionStringBuilder.append("\n")
880         aboutVersionStringBuilder.append(fanboySocialTextView.text)
881         aboutVersionStringBuilder.append("\n")
882         aboutVersionStringBuilder.append(ultraListTextView.text)
883         aboutVersionStringBuilder.append("\n")
884         aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
885         aboutVersionStringBuilder.append("\n\n")
886         aboutVersionStringBuilder.append(packageSignatureTextView.text)
887         aboutVersionStringBuilder.append("\n")
888         aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
889         aboutVersionStringBuilder.append("\n")
890         aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
891         aboutVersionStringBuilder.append("\n")
892         aboutVersionStringBuilder.append(certificateStartDateTextView.text)
893         aboutVersionStringBuilder.append("\n")
894         aboutVersionStringBuilder.append(certificateEndDateTextView.text)
895         aboutVersionStringBuilder.append("\n")
896         aboutVersionStringBuilder.append(certificateVersionTextView.text)
897         aboutVersionStringBuilder.append("\n")
898         aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
899         aboutVersionStringBuilder.append("\n")
900         aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
901
902         // Return the string.
903         return aboutVersionStringBuilder.toString()
904     }
905 }