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