]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.kt
Remove the SaveDialog. https://redmine.stoutner.com/issues/769
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / fragments / AboutVersionFragment.kt
1 /*
2  * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser 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 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.  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 webViewProviderTextView: TextView
109     private lateinit var webViewVersionTextView: TextView
110     private lateinit var orbotTextView: TextView
111     private lateinit var i2pTextView: TextView
112     private lateinit var openKeychainTextView: TextView
113     private lateinit var memoryUsageTextView: TextView
114     private lateinit var appConsumedMemoryTextView: TextView
115     private lateinit var appAvailableMemoryTextView: TextView
116     private lateinit var appTotalMemoryTextView: TextView
117     private lateinit var appMaximumMemoryTextView: TextView
118     private lateinit var systemConsumedMemoryTextView: TextView
119     private lateinit var systemAvailableMemoryTextView: TextView
120     private lateinit var systemTotalMemoryTextView: TextView
121     private lateinit var blocklistsTextView: TextView
122     private lateinit var easyListTextView: TextView
123     private lateinit var easyPrivacyTextView: TextView
124     private lateinit var fanboyAnnoyanceTextView: TextView
125     private lateinit var fanboySocialTextView: TextView
126     private lateinit var ultraListTextView: TextView
127     private lateinit var ultraPrivacyTextView: TextView
128     private lateinit var packageSignatureTextView: TextView
129     private lateinit var certificateIssuerDnTextView: TextView
130     private lateinit var certificateSubjectDnTextView: TextView
131     private lateinit var certificateStartDateTextView: TextView
132     private lateinit var certificateEndDateTextView: TextView
133     private lateinit var certificateVersionTextView: TextView
134     private lateinit var certificateSerialNumberTextView: TextView
135     private lateinit var certificateSignatureAlgorithmTextView: TextView
136
137     companion object {
138         fun createTab(blocklistVersions: Array<String>): AboutVersionFragment {
139             // Create an arguments bundle.
140             val argumentsBundle = Bundle()
141
142             // Store the arguments in the bundle.
143             argumentsBundle.putStringArray(BLOCKLIST_VERSIONS, blocklistVersions)
144
145             // Create a new instance of the tab fragment.
146             val aboutVersionFragment = AboutVersionFragment()
147
148             // Add the arguments bundle to the fragment.
149             aboutVersionFragment.arguments = argumentsBundle
150
151             // Return the new fragment.
152             return aboutVersionFragment
153         }
154     }
155
156     // Define the save about version text activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
157     private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) { fileNameUri: Uri? ->
158         // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
159         if (fileNameUri != null) {
160             try {
161                 // Get the about version string.
162                 val aboutVersionString = getAboutVersionString()
163
164                 // Open an output stream.
165                 val outputStream = requireActivity().contentResolver.openOutputStream(fileNameUri)!!
166
167                 // Write the about version string to the output stream.
168                 outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
169
170                 // Close the output stream.
171                 outputStream.close()
172
173                 // Initialize the file name string from the file name URI last path segment.
174                 var fileNameString = fileNameUri.lastPathSegment
175
176                 // Query the exact file name if the API >= 26.
177                 if (Build.VERSION.SDK_INT >= 26) {
178                     // Get a cursor from the content resolver.
179                     val contentResolverCursor = requireActivity().contentResolver.query(fileNameUri, null, null, null)!!
180
181                     // Move to the first row.
182                     contentResolverCursor.moveToFirst()
183
184                     // Get the file name from the cursor.
185                     fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
186
187                     // Close the cursor.
188                     contentResolverCursor.close()
189                 }
190
191                 // Display a snackbar with the saved logcat information.
192                 Snackbar.make(aboutVersionLayout, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
193             } catch (exception: Exception) {
194                 // Display a snackbar with the error message.
195                 Snackbar.make(aboutVersionLayout, getString(R.string.error_saving_file) + "  " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show()
196             }
197         }
198     }
199
200     // Define the save about version image activity result launcher.  It must be defined before `onCreate()` is run or the app will crash.
201     private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) { fileNameUri: Uri? ->
202         // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
203         if (fileNameUri != null) {
204             // Save the about version image.
205             SaveAboutVersionImage(requireActivity(), fileNameUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout)).execute()
206         }
207     }
208
209     override fun onCreate(savedInstanceState: Bundle?) {
210         // Run the default commands.
211         super.onCreate(savedInstanceState)
212
213         // Store the arguments in class variables.
214         blocklistVersions = requireArguments().getStringArray(BLOCKLIST_VERSIONS)!!
215
216         // Enable the options menu for this fragment.
217         setHasOptionsMenu(true)
218     }
219
220     override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
221         // 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.
222         aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
223
224         // Get handles for the views.
225         privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
226         versionTextView = aboutVersionLayout.findViewById(R.id.version)
227         hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
228         brandTextView = aboutVersionLayout.findViewById(R.id.brand)
229         manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
230         modelTextView = aboutVersionLayout.findViewById(R.id.model)
231         deviceTextView = aboutVersionLayout.findViewById(R.id.device)
232         bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
233         radioTextView = aboutVersionLayout.findViewById(R.id.radio)
234         softwareTextView = aboutVersionLayout.findViewById(R.id.software)
235         androidTextView = aboutVersionLayout.findViewById(R.id.android)
236         securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
237         buildTextView = aboutVersionLayout.findViewById(R.id.build)
238         webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
239         webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
240         orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
241         i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
242         openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
243         memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
244         appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
245         appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
246         appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
247         appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
248         systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
249         systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
250         systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
251         blocklistsTextView = aboutVersionLayout.findViewById(R.id.blocklists)
252         easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
253         easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
254         fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
255         fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
256         ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
257         ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
258         packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
259         certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
260         certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
261         certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
262         certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
263         certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
264         certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
265         certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
266
267         // Setup the labels.
268         val version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + BuildConfig.VERSION_CODE + ")"
269         val brandLabel = getString(R.string.brand) + "  "
270         val manufacturerLabel = getString(R.string.manufacturer) + "  "
271         val modelLabel = getString(R.string.model) + "  "
272         val deviceLabel = getString(R.string.device) + "  "
273         val bootloaderLabel = getString(R.string.bootloader) + "  "
274         val androidLabel = getString(R.string.android) + "  "
275         val buildLabel = getString(R.string.build) + "  "
276         val webViewVersionLabel = getString(R.string.webview_version) + "  "
277         appConsumedMemoryLabel = getString(R.string.app_consumed_memory) + "  "
278         appAvailableMemoryLabel = getString(R.string.app_available_memory) + "  "
279         appTotalMemoryLabel = getString(R.string.app_total_memory) + "  "
280         appMaximumMemoryLabel = getString(R.string.app_maximum_memory) + "  "
281         systemConsumedMemoryLabel = getString(R.string.system_consumed_memory) + "  "
282         systemAvailableMemoryLabel = getString(R.string.system_available_memory) + "  "
283         systemTotalMemoryLabel = getString(R.string.system_total_memory) + "  "
284         val easyListLabel = getString(R.string.easylist_label) + "  "
285         val easyPrivacyLabel = getString(R.string.easyprivacy_label) + "  "
286         val fanboyAnnoyanceLabel = getString(R.string.fanboy_annoyance_label) + "  "
287         val fanboySocialLabel = getString(R.string.fanboy_social_label) + "  "
288         val ultraListLabel = getString(R.string.ultralist_label) + "  "
289         val ultraPrivacyLabel = getString(R.string.ultraprivacy_label) + "  "
290         val issuerDNLabel = getString(R.string.issuer_dn) + "  "
291         val subjectDNLabel = getString(R.string.subject_dn) + "  "
292         val startDateLabel = getString(R.string.start_date) + "  "
293         val endDateLabel = getString(R.string.end_date) + "  "
294         val certificateVersionLabel = getString(R.string.certificate_version) + "  "
295         val serialNumberLabel = getString(R.string.serial_number) + "  "
296         val signatureAlgorithmLabel = getString(R.string.signature_algorithm) + "  "
297
298         // 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.
299         // Once the minimum API >= 26 this can be accomplished with the WebView package info.
300         val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false)
301         val tabLayoutWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
302         val userAgentString = tabLayoutWebView.settings.userAgentString
303
304         // Get the device's information and store it in strings.
305         val brand = Build.BRAND
306         val manufacturer = Build.MANUFACTURER
307         val model = Build.MODEL
308         val device = Build.DEVICE
309         val bootloader = Build.BOOTLOADER
310         val radio = Build.getRadioVersion()
311         val android = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Build.VERSION.SDK_INT + ")"
312         val build = Build.DISPLAY
313         // Select the substring that begins after `Chrome/` and goes until the next ` `.
314         val webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")))
315
316         // Get the Orbot version name if Orbot is installed.
317         val orbot: String = try {
318             // Store the version name.
319             requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
320         } catch (exception: PackageManager.NameNotFoundException) {  // Orbot is not installed.
321             // Store an empty string.
322             ""
323         }
324
325         // Get the I2P version name if I2P is installed.
326         val i2p: String = try {
327             // Store the version name.
328             requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName
329         } catch (exception: PackageManager.NameNotFoundException) {  // I2P is not installed.
330             // Store an empty string.
331             ""
332         }
333
334         // Get the OpenKeychain version name if it is installed.
335         val openKeychain: String = try {
336             // Store the version name.
337             requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
338         } catch (exception: PackageManager.NameNotFoundException) {  // OpenKeychain is not installed.
339             // Store an empty string.
340             ""
341         }
342
343         // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
344         val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
345         val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
346         val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
347         val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
348         val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
349         val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
350         val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
351         val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
352         val easyListStringBuilder = SpannableStringBuilder(easyListLabel + blocklistVersions[0])
353         val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1])
354         val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2])
355         val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3])
356         val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + blocklistVersions[4])
357         val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5])
358
359         // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until the minimum API >= 23.
360         blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.about_version_blue_text))
361
362         // Set the spans to display the device information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
363         brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
364         manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
365         modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
366         deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
367         bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
368         androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
369         buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
370         webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
371         easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
372         easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
373         fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
374         fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
375         ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
376         ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
377
378         // Display the strings in the text boxes.
379         versionTextView.text = version
380         brandTextView.text = brandStringBuilder
381         manufacturerTextView.text = manufacturerStringBuilder
382         modelTextView.text = modelStringBuilder
383         deviceTextView.text = deviceStringBuilder
384         bootloaderTextView.text = bootloaderStringBuilder
385         androidTextView.text = androidStringBuilder
386         buildTextView.text = buildStringBuilder
387         webViewVersionTextView.text = webViewVersionStringBuilder
388         easyListTextView.text = easyListStringBuilder
389         easyPrivacyTextView.text = easyPrivacyStringBuilder
390         fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
391         fanboySocialTextView.text = fanboySocialStringBuilder
392         ultraListTextView.text = ultraListStringBuilder
393         ultraPrivacyTextView.text = ultraPrivacyStringBuilder
394
395         // Only populate the radio text view if there is a radio in the device.
396         // 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>
397         if (radio != null && radio.isNotEmpty()) {
398             // Setup the label.
399             val radioLabel = getString(R.string.radio) + "  "
400
401             // Create a spannable string builder.
402             val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
403
404             // Set the span to display the radio in blue.
405             radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
406
407             // Display the string in the text view.
408             radioTextView.text = radioStringBuilder
409         } else {  // This device does not have a radio.
410             // Hide the radio text view.
411             radioTextView.visibility = View.GONE
412         }
413
414         // Build.VERSION.SECURITY_PATCH is only available for SDK_INT >= 23.
415         if (Build.VERSION.SDK_INT >= 23) {
416             // Setup the label.
417             val securityPatchLabel = getString(R.string.security_patch) + "  "
418
419             // Get the security patch version.
420             val securityPatch = Build.VERSION.SECURITY_PATCH
421
422             // Create a spannable string builder.
423             val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
424
425             // Set the span to display the security patch version in blue.
426             securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
427
428             // Display the string in the text view.
429             securityPatchTextView.text = securityPatchStringBuilder
430         } else {  // The API < 23.
431             // Hide the security patch text view.
432             securityPatchTextView.visibility = View.GONE
433         }
434
435         // Only populate the WebView provider if the SDK >= 21.
436         if (Build.VERSION.SDK_INT >= 21) {
437             // Create the WebView provider label.
438             val webViewProviderLabel = getString(R.string.webview_provider) + "  "
439
440             // Get the current WebView package info.
441             val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
442
443             // Get the WebView provider name.
444             val webViewPackageName = webViewPackageInfo.packageName
445
446             // Create the spannable string builder.
447             val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
448
449             // Apply the coloration.
450             webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
451
452             // Display the WebView provider.
453             webViewProviderTextView.text = webViewProviderStringBuilder
454         } else {  // The API < 21.
455             // Hide the WebView provider text view.
456             webViewProviderTextView.visibility = View.GONE
457         }
458
459         // Only populate the Orbot text view if it is installed.
460         if (orbot.isNotEmpty()) {
461             // Setup the label.
462             val orbotLabel = getString(R.string.orbot) + "  "
463
464             // Create a spannable string builder.
465             val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
466
467             // Set the span to display the Orbot version.
468             orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
469
470             // Display the string in the text view.
471             orbotTextView.text = orbotStringBuilder
472         } else {  // Orbot is not installed.
473             // Hide the Orbot text view.
474             orbotTextView.visibility = View.GONE
475         }
476
477         // Only populate the I2P text view if it is installed.
478         if (i2p.isNotEmpty()) {
479             // Setup the label.
480             val i2pLabel = getString(R.string.i2p) + "  "
481
482             // Create a spannable string builder.
483             val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
484
485             // Set the span to display the I2P version.
486             i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
487
488             // Display the string in the text view.
489             i2pTextView.text = i2pStringBuilder
490         } else {  // I2P is not installed.
491             // Hide the I2P text view.
492             i2pTextView.visibility = View.GONE
493         }
494
495         // Only populate the OpenKeychain text view if it is installed.
496         if (openKeychain.isNotEmpty()) {
497             // Setup the label.
498             val openKeychainLabel = getString(R.string.openkeychain) + "  "
499
500             // Create a spannable string builder.
501             val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
502
503             // Set the span to display the OpenKeychain version.
504             openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
505
506             // Display the string in the text view.
507             openKeychainTextView.text = openKeychainStringBuilder
508         } else {  //OpenKeychain is not installed.
509             // Hide the OpenKeychain text view.
510             openKeychainTextView.visibility = View.GONE
511         }
512
513         // Display the package signature.
514         try {
515             // Get the first package signature.  Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
516             // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead.
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         if (webViewProviderTextView.visibility == View.VISIBLE) {
823             aboutVersionStringBuilder.append(webViewProviderTextView.text)
824             aboutVersionStringBuilder.append("\n")
825         }
826         aboutVersionStringBuilder.append(webViewVersionTextView.text)
827         aboutVersionStringBuilder.append("\n")
828         if (orbotTextView.visibility == View.VISIBLE) {
829             aboutVersionStringBuilder.append(orbotTextView.text)
830             aboutVersionStringBuilder.append("\n")
831         }
832         if (i2pTextView.visibility == View.VISIBLE) {
833             aboutVersionStringBuilder.append(i2pTextView.text)
834             aboutVersionStringBuilder.append("\n")
835         }
836         if (openKeychainTextView.visibility == View.VISIBLE) {
837             aboutVersionStringBuilder.append(openKeychainTextView.text)
838             aboutVersionStringBuilder.append("\n")
839         }
840         aboutVersionStringBuilder.append("\n")
841         aboutVersionStringBuilder.append(memoryUsageTextView.text)
842         aboutVersionStringBuilder.append("\n")
843         aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
844         aboutVersionStringBuilder.append("\n")
845         aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
846         aboutVersionStringBuilder.append("\n")
847         aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
848         aboutVersionStringBuilder.append("\n")
849         aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
850         aboutVersionStringBuilder.append("\n")
851         aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
852         aboutVersionStringBuilder.append("\n")
853         aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
854         aboutVersionStringBuilder.append("\n")
855         aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
856         aboutVersionStringBuilder.append("\n\n")
857         aboutVersionStringBuilder.append(blocklistsTextView.text)
858         aboutVersionStringBuilder.append("\n")
859         aboutVersionStringBuilder.append(easyListTextView.text)
860         aboutVersionStringBuilder.append("\n")
861         aboutVersionStringBuilder.append(easyPrivacyTextView.text)
862         aboutVersionStringBuilder.append("\n")
863         aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
864         aboutVersionStringBuilder.append("\n")
865         aboutVersionStringBuilder.append(fanboySocialTextView.text)
866         aboutVersionStringBuilder.append("\n")
867         aboutVersionStringBuilder.append(ultraListTextView.text)
868         aboutVersionStringBuilder.append("\n")
869         aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
870         aboutVersionStringBuilder.append("\n\n")
871         aboutVersionStringBuilder.append(packageSignatureTextView.text)
872         aboutVersionStringBuilder.append("\n")
873         aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
874         aboutVersionStringBuilder.append("\n")
875         aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
876         aboutVersionStringBuilder.append("\n")
877         aboutVersionStringBuilder.append(certificateStartDateTextView.text)
878         aboutVersionStringBuilder.append("\n")
879         aboutVersionStringBuilder.append(certificateEndDateTextView.text)
880         aboutVersionStringBuilder.append("\n")
881         aboutVersionStringBuilder.append(certificateVersionTextView.text)
882         aboutVersionStringBuilder.append("\n")
883         aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
884         aboutVersionStringBuilder.append("\n")
885         aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
886
887         // Return the string.
888         return aboutVersionStringBuilder.toString()
889     }
890 }