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