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