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