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