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