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