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