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.BuildConfig
55 import com.stoutner.privacybrowser.R
56 import com.stoutner.privacybrowser.coroutines.SaveAboutVersionImageCoroutine
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) {
166 // Initialize the file name string from the file URI last path segment.
167 var fileNameString = fileUri.lastPathSegment
169 // Query the exact file name if the API >= 26.
170 if (Build.VERSION.SDK_INT >= 26) {
171 // Get a cursor from the content resolver.
172 val contentResolverCursor = requireActivity().contentResolver.query(fileUri, null, null, null)!!
174 // Move to the first row.
175 contentResolverCursor.moveToFirst()
177 // Get the file name from the cursor.
178 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
181 contentResolverCursor.close()
185 // Get the about version string.
186 val aboutVersionString = getAboutVersionString()
188 // Open an output stream.
189 val outputStream = requireActivity().contentResolver.openOutputStream(fileUri)!!
191 // Save about version using a coroutine with Dispatchers.IO.
192 CoroutineScope(Dispatchers.Main).launch {
193 withContext(Dispatchers.IO) {
194 // Write the about version string to the output stream.
195 outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
197 // Close the output stream.
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, fileNameString, 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 // Save the file if the URI is not null, which happens if the user exits the file picker by pressing back.
215 SaveAboutVersionImageCoroutine.saveImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout))
218 override fun onCreate(savedInstanceState: Bundle?) {
219 // Run the default commands.
220 super.onCreate(savedInstanceState)
222 // Store the arguments in class variables.
223 blocklistVersions = requireArguments().getStringArray(BLOCKLIST_VERSIONS)!!
225 // Enable the options menu for this fragment.
226 setHasOptionsMenu(true)
229 override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
230 // 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.
231 aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
233 // Get handles for the views.
234 privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
235 versionTextView = aboutVersionLayout.findViewById(R.id.version)
236 hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
237 brandTextView = aboutVersionLayout.findViewById(R.id.brand)
238 manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
239 modelTextView = aboutVersionLayout.findViewById(R.id.model)
240 deviceTextView = aboutVersionLayout.findViewById(R.id.device)
241 bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
242 radioTextView = aboutVersionLayout.findViewById(R.id.radio)
243 softwareTextView = aboutVersionLayout.findViewById(R.id.software)
244 androidTextView = aboutVersionLayout.findViewById(R.id.android)
245 securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
246 buildTextView = aboutVersionLayout.findViewById(R.id.build)
247 kernelTextView = aboutVersionLayout.findViewById(R.id.kernel)
248 webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
249 webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
250 orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
251 i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
252 openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
253 memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
254 appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
255 appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
256 appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
257 appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
258 systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
259 systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
260 systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
261 blocklistsTextView = aboutVersionLayout.findViewById(R.id.blocklists)
262 easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
263 easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
264 fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
265 fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
266 ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
267 ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
268 packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
269 certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
270 certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
271 certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
272 certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
273 certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
274 certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
275 certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
278 val version = getString(R.string.version_code, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
279 val brandLabel = getString(R.string.brand) + " "
280 val manufacturerLabel = getString(R.string.manufacturer) + " "
281 val modelLabel = getString(R.string.model) + " "
282 val deviceLabel = getString(R.string.device) + " "
283 val bootloaderLabel = getString(R.string.bootloader) + " "
284 val androidLabel = getString(R.string.android) + " "
285 val buildLabel = getString(R.string.build) + " "
286 val kernelLabel = getString(R.string.kernel) + " "
287 val webViewVersionLabel = getString(R.string.webview_version) + " "
288 appConsumedMemoryLabel = getString(R.string.app_consumed_memory) + " "
289 appAvailableMemoryLabel = getString(R.string.app_available_memory) + " "
290 appTotalMemoryLabel = getString(R.string.app_total_memory) + " "
291 appMaximumMemoryLabel = getString(R.string.app_maximum_memory) + " "
292 systemConsumedMemoryLabel = getString(R.string.system_consumed_memory) + " "
293 systemAvailableMemoryLabel = getString(R.string.system_available_memory) + " "
294 systemTotalMemoryLabel = getString(R.string.system_total_memory) + " "
295 val easyListLabel = getString(R.string.easylist_label) + " "
296 val easyPrivacyLabel = getString(R.string.easyprivacy_label) + " "
297 val fanboyAnnoyanceLabel = getString(R.string.fanboys_annoyance_label) + " "
298 val fanboySocialLabel = getString(R.string.fanboys_social_label) + " "
299 val ultraListLabel = getString(R.string.ultralist_label) + " "
300 val ultraPrivacyLabel = getString(R.string.ultraprivacy_label) + " "
301 val issuerDNLabel = getString(R.string.issuer_dn) + " "
302 val subjectDNLabel = getString(R.string.subject_dn) + " "
303 val startDateLabel = getString(R.string.start_date) + " "
304 val endDateLabel = getString(R.string.end_date) + " "
305 val certificateVersionLabel = getString(R.string.certificate_version) + " "
306 val serialNumberLabel = getString(R.string.serial_number) + " "
307 val signatureAlgorithmLabel = getString(R.string.signature_algorithm) + " "
309 // 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.
310 // Once the minimum API >= 26 this can be accomplished with the WebView package info.
311 val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false)
312 val tabLayoutWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
313 val userAgentString = tabLayoutWebView.settings.userAgentString
315 // Get the device's information and store it in strings.
316 val brand = Build.BRAND
317 val manufacturer = Build.MANUFACTURER
318 val model = Build.MODEL
319 val device = Build.DEVICE
320 val bootloader = Build.BOOTLOADER
321 val radio = Build.getRadioVersion()
322 val android = getString(R.string.api, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)
323 val build = Build.DISPLAY
324 val kernel = System.getProperty("os.version")
326 // Get the WebView version, selecting the substring that begins after `Chrome/` and goes until the next ` `.
327 val webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")))
329 // Get the Orbot version name if Orbot is installed.
330 val orbot: String = try {
331 // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33.
332 @Suppress("DEPRECATION")
333 requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
334 } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed.
335 // Store an empty string.
339 // Get the I2P version name if I2P is installed.
340 val i2p: String = try {
341 // Check to see if the F-Droid flavor is installed. The newer `getPackageInfo()` may be used once the minimum API >= 33.
342 @Suppress("DEPRECATION")
343 requireContext().getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
344 } catch (exception: PackageManager.NameNotFoundException) { // The F-Droid flavor is not installed.
346 // Check to see if the F-Droid flavor is installed. The newer `getPackageInfo()` may be used once the minimum API >= 33.
347 @Suppress("DEPRECATION")
348 requireContext().getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
349 } catch (exception: PackageManager.NameNotFoundException) { // The Google Play flavor is not installed either.
350 // Store an empty string.
355 // Get the OpenKeychain version name if it is installed.
356 val openKeychain: String = try {
357 // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33.
358 @Suppress("DEPRECATION")
359 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
360 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
361 // Store an empty string.
365 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
366 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
367 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
368 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
369 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
370 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
371 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
372 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
373 val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
374 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
375 val easyListStringBuilder = SpannableStringBuilder(easyListLabel + blocklistVersions[0])
376 val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1])
377 val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2])
378 val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3])
379 val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + blocklistVersions[4])
380 val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5])
382 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
383 blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
385 // Set the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
386 brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
387 manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
388 modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389 deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390 bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
391 androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
393 kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
394 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
395 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
396 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
397 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
398 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
399 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
400 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
402 // Display the strings in the text boxes.
403 versionTextView.text = version
404 brandTextView.text = brandStringBuilder
405 manufacturerTextView.text = manufacturerStringBuilder
406 modelTextView.text = modelStringBuilder
407 deviceTextView.text = deviceStringBuilder
408 bootloaderTextView.text = bootloaderStringBuilder
409 androidTextView.text = androidStringBuilder
410 buildTextView.text = buildStringBuilder
411 kernelTextView.text = kernelStringBuilder
412 webViewVersionTextView.text = webViewVersionStringBuilder
413 easyListTextView.text = easyListStringBuilder
414 easyPrivacyTextView.text = easyPrivacyStringBuilder
415 fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
416 fanboySocialTextView.text = fanboySocialStringBuilder
417 ultraListTextView.text = ultraListStringBuilder
418 ultraPrivacyTextView.text = ultraPrivacyStringBuilder
420 // Only populate the radio text view if there is a radio in the device.
421 // 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>
422 if (radio != null && radio.isNotEmpty()) {
424 val radioLabel = getString(R.string.radio) + " "
426 // Create a spannable string builder.
427 val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
429 // Set the span to display the radio in blue.
430 radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
432 // Display the string in the text view.
433 radioTextView.text = radioStringBuilder
434 } else { // This device does not have a radio.
435 // Hide the radio text view.
436 radioTextView.visibility = View.GONE
440 val securityPatchLabel = getString(R.string.security_patch) + " "
442 // Get the security patch version.
443 val securityPatch = Build.VERSION.SECURITY_PATCH
445 // Create a spannable string builder.
446 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
448 // Set the span to display the security patch version in blue.
449 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
451 // Display the string in the text view.
452 securityPatchTextView.text = securityPatchStringBuilder
454 // Create the WebView provider label.
455 val webViewProviderLabel = getString(R.string.webview_provider) + " "
457 // Get the current WebView package info.
458 val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
460 // Get the WebView provider name.
461 val webViewPackageName = webViewPackageInfo.packageName
463 // Create the spannable string builder.
464 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
466 // Apply the coloration.
467 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
469 // Display the WebView provider.
470 webViewProviderTextView.text = webViewProviderStringBuilder
472 // Only populate the Orbot text view if it is installed.
473 if (orbot.isNotEmpty()) {
475 val orbotLabel = getString(R.string.orbot) + " "
477 // Create a spannable string builder.
478 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
480 // Set the span to display the Orbot version.
481 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
483 // Display the string in the text view.
484 orbotTextView.text = orbotStringBuilder
485 } else { // Orbot is not installed.
486 // Hide the Orbot text view.
487 orbotTextView.visibility = View.GONE
490 // Only populate the I2P text view if it is installed.
491 if (i2p.isNotEmpty()) {
493 val i2pLabel = getString(R.string.i2p) + " "
495 // Create a spannable string builder.
496 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
498 // Set the span to display the I2P version.
499 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
501 // Display the string in the text view.
502 i2pTextView.text = i2pStringBuilder
503 } else { // I2P is not installed.
504 // Hide the I2P text view.
505 i2pTextView.visibility = View.GONE
508 // Only populate the OpenKeychain text view if it is installed.
509 if (openKeychain.isNotEmpty()) {
511 val openKeychainLabel = getString(R.string.openkeychain) + " "
513 // Create a spannable string builder.
514 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
516 // Set the span to display the OpenKeychain version.
517 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
519 // Display the string in the text view.
520 openKeychainTextView.text = openKeychainStringBuilder
521 } else { //OpenKeychain is not installed.
522 // Hide the OpenKeychain text view.
523 openKeychainTextView.visibility = View.GONE
526 // Display the package signature.
528 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
529 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
530 @Suppress("DEPRECATION")
531 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
534 // Convert the signature to a byte array input stream.
535 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
537 // Display the certificate information on the screen.
539 // Instantiate a certificate factory.
540 val certificateFactory = CertificateFactory.getInstance("X509")
542 // Generate an X509 certificate.
543 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
545 // Store the individual sections of the certificate.
546 val issuerDNPrincipal = x509Certificate.issuerDN
547 val subjectDNPrincipal = x509Certificate.subjectDN
548 val startDate = x509Certificate.notBefore
549 val endDate = x509Certificate.notAfter
550 val certificateVersion = x509Certificate.version
551 val serialNumberBigInteger = x509Certificate.serialNumber
552 val signatureAlgorithmNameString = x509Certificate.sigAlgName
554 // Create a spannable string builder for each text view that needs multiple colors of text.
555 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
556 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
557 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
558 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
559 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
560 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
561 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
563 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
564 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
565 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
566 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
567 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
568 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
569 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
570 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
572 // Display the strings in the text boxes.
573 certificateIssuerDnTextView.text = issuerDNStringBuilder
574 certificateSubjectDnTextView.text = subjectDNStringBuilder
575 certificateStartDateTextView.text = startDateStringBuilder
576 certificateEndDateTextView.text = endDataStringBuilder
577 certificateVersionTextView.text = certificateVersionStringBuilder
578 certificateSerialNumberTextView.text = serialNumberStringBuilder
579 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
580 } catch (certificateException: CertificateException) {
581 // Do nothing if there is a certificate error.
584 // Get a handle for the runtime.
585 runtime = Runtime.getRuntime()
587 // Get a handle for the activity manager.
588 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
590 // Instantiate a memory info variable.
591 memoryInfo = ActivityManager.MemoryInfo()
593 // Define a number format.
594 numberFormat = NumberFormat.getInstance()
596 // Set the minimum and maximum number of fraction digits.
597 numberFormat.minimumFractionDigits = 2
598 numberFormat.maximumFractionDigits = 2
600 // Update the memory usage.
601 updateMemoryUsage(requireActivity())
602 } catch (e: PackageManager.NameNotFoundException) {
603 // Do nothing if the package manager says Privacy Browser isn't installed.
606 // Scroll the tab if the saved instance state is not null.
607 if (savedInstanceState != null) {
608 aboutVersionLayout.post {
609 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
610 aboutVersionLayout.scrollY = savedInstanceState.getInt("scroll_y")
614 // Return the tab layout.
615 return aboutVersionLayout
618 override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
619 // Inflate the about version menu.
620 menuInflater.inflate(R.menu.about_version_options_menu, menu)
622 // Run the default commands.
623 super.onCreateOptionsMenu(menu, menuInflater)
626 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
627 // Run the appropriate commands.
628 when (menuItem.itemId) {
629 R.id.copy -> { // Copy.
630 // Get the about version string.
631 val aboutVersionString = getAboutVersionString()
633 // Get a handle for the clipboard manager.
634 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
636 // Save the about version string in a clip data.
637 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
639 // Place the clip data on the clipboard.
640 clipboardManager.setPrimaryClip(aboutVersionClipData)
642 // Display a snackbar.
643 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
645 // Consume the event.
649 R.id.share -> { // Share.
650 // Get the about version string.
651 val aboutString = getAboutVersionString()
653 // Create an email intent.
654 val emailIntent = Intent(Intent.ACTION_SEND)
656 // Add the about version string to the intent.
657 emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
659 // Set the MIME type.
660 emailIntent.type = "text/plain"
662 // Set the intent to open in a new task.
663 emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
666 startActivity(Intent.createChooser(emailIntent, getString(R.string.share)))
668 // Consume the event.
672 R.id.save_text -> { // Save text.
673 // Open the file picker.
674 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
676 // Consume the event.
680 R.id.save_image -> { // Save image.
681 // Open the file picker.
682 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
684 // Consume the event.
687 else -> { // The home button was selected.
688 // Run the parents class on return.
689 return super.onOptionsItemSelected(menuItem)
694 override fun onSaveInstanceState(savedInstanceState: Bundle) {
695 // Run the default commands.
696 super.onSaveInstanceState(savedInstanceState)
698 // Save the scroll positions.
699 savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
700 savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
703 override fun onPause() {
704 // Run the default commands.
707 // Pause the updating of the memory usage.
708 updateMemoryUsageBoolean = false
711 override fun onResume() {
712 // Run the default commands.
715 // Resume the updating of the memory usage.
716 updateMemoryUsageBoolean = true
719 fun updateMemoryUsage(activity: Activity) {
721 // Update the memory usage if enabled.
722 if (updateMemoryUsageBoolean) {
723 // Populate the memory info variable.
724 activityManager.getMemoryInfo(memoryInfo)
726 // Get the app memory information.
727 val appAvailableMemoryLong = runtime.freeMemory()
728 val appTotalMemoryLong = runtime.totalMemory()
729 val appMaximumMemoryLong = runtime.maxMemory()
731 // Calculate the app consumed memory.
732 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
734 // Get the system memory information.
735 val systemTotalMemoryLong = memoryInfo.totalMem
736 val systemAvailableMemoryLong = memoryInfo.availMem
738 // Calculate the system consumed memory.
739 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
741 // Convert the memory information into mebibytes.
742 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
743 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
744 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
745 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
746 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
747 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
748 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
750 // Get the mebibyte string.
751 val mebibyte = getString(R.string.mebibyte)
753 // Calculate the mebibyte length.
754 val mebibyteLength = mebibyte.length
756 // Create spannable string builders.
757 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
758 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
759 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
760 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
761 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
762 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
763 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
765 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
766 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
767 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
768 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
769 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
770 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
771 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
772 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
774 // Display the string in the text boxes.
775 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
776 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
777 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
778 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
779 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
780 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
781 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
784 // Schedule another memory update if the activity has not been destroyed.
785 if (!activity.isDestroyed) {
786 // Create a handler to update the memory usage.
787 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
789 // Create a runnable to update the memory usage.
790 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
792 // Update the memory usage after 1000 milliseconds
793 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
795 } catch (exception: Exception) {
800 fun getAboutVersionString(): String {
801 // Initialize an about version string builder.
802 val aboutVersionStringBuilder = StringBuilder()
804 // Populate the about version string builder.
805 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
806 aboutVersionStringBuilder.append("\n")
807 aboutVersionStringBuilder.append(versionTextView.text)
808 aboutVersionStringBuilder.append("\n\n")
809 aboutVersionStringBuilder.append(hardwareTextView.text)
810 aboutVersionStringBuilder.append("\n")
811 aboutVersionStringBuilder.append(brandTextView.text)
812 aboutVersionStringBuilder.append("\n")
813 aboutVersionStringBuilder.append(manufacturerTextView.text)
814 aboutVersionStringBuilder.append("\n")
815 aboutVersionStringBuilder.append(modelTextView.text)
816 aboutVersionStringBuilder.append("\n")
817 aboutVersionStringBuilder.append(deviceTextView.text)
818 aboutVersionStringBuilder.append("\n")
819 aboutVersionStringBuilder.append(bootloaderTextView.text)
820 aboutVersionStringBuilder.append("\n")
821 if (radioTextView.visibility == View.VISIBLE) {
822 aboutVersionStringBuilder.append(radioTextView.text)
823 aboutVersionStringBuilder.append("\n")
825 aboutVersionStringBuilder.append("\n")
826 aboutVersionStringBuilder.append(softwareTextView.text)
827 aboutVersionStringBuilder.append("\n")
828 aboutVersionStringBuilder.append(androidTextView.text)
829 aboutVersionStringBuilder.append("\n")
830 if (securityPatchTextView.visibility == View.VISIBLE) {
831 aboutVersionStringBuilder.append(securityPatchTextView.text)
832 aboutVersionStringBuilder.append("\n")
834 aboutVersionStringBuilder.append(buildTextView.text)
835 aboutVersionStringBuilder.append("\n")
836 aboutVersionStringBuilder.append(kernelTextView.text)
837 aboutVersionStringBuilder.append("\n")
838 if (webViewProviderTextView.visibility == View.VISIBLE) {
839 aboutVersionStringBuilder.append(webViewProviderTextView.text)
840 aboutVersionStringBuilder.append("\n")
842 aboutVersionStringBuilder.append(webViewVersionTextView.text)
843 aboutVersionStringBuilder.append("\n")
844 if (orbotTextView.visibility == View.VISIBLE) {
845 aboutVersionStringBuilder.append(orbotTextView.text)
846 aboutVersionStringBuilder.append("\n")
848 if (i2pTextView.visibility == View.VISIBLE) {
849 aboutVersionStringBuilder.append(i2pTextView.text)
850 aboutVersionStringBuilder.append("\n")
852 if (openKeychainTextView.visibility == View.VISIBLE) {
853 aboutVersionStringBuilder.append(openKeychainTextView.text)
854 aboutVersionStringBuilder.append("\n")
856 aboutVersionStringBuilder.append("\n")
857 aboutVersionStringBuilder.append(memoryUsageTextView.text)
858 aboutVersionStringBuilder.append("\n")
859 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
860 aboutVersionStringBuilder.append("\n")
861 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
862 aboutVersionStringBuilder.append("\n")
863 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
864 aboutVersionStringBuilder.append("\n")
865 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
866 aboutVersionStringBuilder.append("\n")
867 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
868 aboutVersionStringBuilder.append("\n")
869 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
870 aboutVersionStringBuilder.append("\n")
871 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
872 aboutVersionStringBuilder.append("\n\n")
873 aboutVersionStringBuilder.append(blocklistsTextView.text)
874 aboutVersionStringBuilder.append("\n")
875 aboutVersionStringBuilder.append(easyListTextView.text)
876 aboutVersionStringBuilder.append("\n")
877 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
878 aboutVersionStringBuilder.append("\n")
879 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
880 aboutVersionStringBuilder.append("\n")
881 aboutVersionStringBuilder.append(fanboySocialTextView.text)
882 aboutVersionStringBuilder.append("\n")
883 aboutVersionStringBuilder.append(ultraListTextView.text)
884 aboutVersionStringBuilder.append("\n")
885 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
886 aboutVersionStringBuilder.append("\n\n")
887 aboutVersionStringBuilder.append(packageSignatureTextView.text)
888 aboutVersionStringBuilder.append("\n")
889 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
890 aboutVersionStringBuilder.append("\n")
891 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
892 aboutVersionStringBuilder.append("\n")
893 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
894 aboutVersionStringBuilder.append("\n")
895 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
896 aboutVersionStringBuilder.append("\n")
897 aboutVersionStringBuilder.append(certificateVersionTextView.text)
898 aboutVersionStringBuilder.append("\n")
899 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
900 aboutVersionStringBuilder.append("\n")
901 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
903 // Return the string.
904 return aboutVersionStringBuilder.toString()