2 * Copyright 2016-2023 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.os.Build
31 import android.os.Bundle
32 import android.os.Handler
33 import android.os.Looper
34 import android.provider.OpenableColumns
35 import android.text.SpannableStringBuilder
36 import android.text.Spanned
37 import android.text.style.ForegroundColorSpan
38 import android.view.LayoutInflater
39 import android.view.Menu
40 import android.view.MenuInflater
41 import android.view.MenuItem
42 import android.view.View
43 import android.view.ViewGroup
44 import android.webkit.WebView
45 import android.widget.TextView
47 import androidx.activity.result.contract.ActivityResultContracts
48 import androidx.fragment.app.Fragment
49 import androidx.webkit.WebViewCompat
51 import com.google.android.material.snackbar.Snackbar
53 import com.stoutner.privacybrowser.BuildConfig
54 import com.stoutner.privacybrowser.R
55 import com.stoutner.privacybrowser.coroutines.SaveAboutVersionImageCoroutine
57 import kotlinx.coroutines.CoroutineScope
58 import kotlinx.coroutines.Dispatchers
59 import kotlinx.coroutines.launch
60 import kotlinx.coroutines.withContext
62 import java.io.ByteArrayInputStream
63 import java.io.InputStream
64 import java.lang.Exception
65 import java.nio.charset.StandardCharsets
66 import java.security.cert.CertificateException
67 import java.security.cert.CertificateFactory
68 import java.security.cert.X509Certificate
69 import java.text.DateFormat
70 import java.text.NumberFormat
72 import kotlin.text.StringBuilder
74 // Define the class constants.
75 private const val FILTERLISTS_VERSIONS = "filterlists_versions"
76 private const val MEBIBYTE = 1048576
78 class AboutVersionFragment : Fragment() {
79 // Define the class variables.
80 private var updateMemoryUsageBoolean = true
82 // Declare the class variables.
83 private lateinit var aboutVersionLayout: View
84 private lateinit var activityManager: ActivityManager
85 private lateinit var appAvailableMemoryLabel: String
86 private lateinit var appConsumedMemoryLabel: String
87 private lateinit var appMaximumMemoryLabel: String
88 private lateinit var appTotalMemoryLabel: String
89 private lateinit var blueColorSpan: ForegroundColorSpan
90 private lateinit var filterListsVersions: Array<String>
91 private lateinit var memoryInfo: ActivityManager.MemoryInfo
92 private lateinit var numberFormat: NumberFormat
93 private lateinit var runtime: Runtime
94 private lateinit var systemAvailableMemoryLabel: String
95 private lateinit var systemConsumedMemoryLabel: String
96 private lateinit var systemTotalMemoryLabel: String
98 // Declare the class views.
99 private lateinit var androidTextView: TextView
100 private lateinit var appAvailableMemoryTextView: TextView
101 private lateinit var appConsumedMemoryTextView: TextView
102 private lateinit var appMaximumMemoryTextView: TextView
103 private lateinit var appTotalMemoryTextView: TextView
104 private lateinit var brandTextView: TextView
105 private lateinit var bootloaderTextView: TextView
106 private lateinit var certificateEndDateTextView: TextView
107 private lateinit var certificateIssuerDnTextView: TextView
108 private lateinit var certificateSerialNumberTextView: TextView
109 private lateinit var certificateSignatureAlgorithmTextView: TextView
110 private lateinit var certificateStartDateTextView: TextView
111 private lateinit var certificateSubjectDnTextView: TextView
112 private lateinit var certificateVersionTextView: TextView
113 private lateinit var buildTextView: TextView
114 private lateinit var deviceTextView: TextView
115 private lateinit var easyListTextView: TextView
116 private lateinit var easyPrivacyTextView: TextView
117 private lateinit var fanboyAnnoyanceTextView: TextView
118 private lateinit var fanboySocialTextView: TextView
119 private lateinit var filterListsTextView: TextView
120 private lateinit var hardwareTextView: TextView
121 private lateinit var i2pTextView: TextView
122 private lateinit var kernelTextView: TextView
123 private lateinit var manufacturerTextView: TextView
124 private lateinit var memoryUsageTextView: TextView
125 private lateinit var modelTextView: TextView
126 private lateinit var openKeychainTextView: TextView
127 private lateinit var orbotTextView: TextView
128 private lateinit var packageSignatureTextView: TextView
129 private lateinit var privacyBrowserTextView: TextView
130 private lateinit var radioTextView: TextView
131 private lateinit var securityPatchTextView: TextView
132 private lateinit var softwareTextView: TextView
133 private lateinit var systemAvailableMemoryTextView: TextView
134 private lateinit var systemConsumedMemoryTextView: TextView
135 private lateinit var systemTotalMemoryTextView: TextView
136 private lateinit var versionTextView: TextView
137 private lateinit var ultraListTextView: TextView
138 private lateinit var ultraPrivacyTextView: TextView
139 private lateinit var webViewProviderTextView: TextView
140 private lateinit var webViewVersionTextView: TextView
143 fun createTab(filterListsVersions: Array<String>): AboutVersionFragment {
144 // Create an arguments bundle.
145 val argumentsBundle = Bundle()
147 // Store the arguments in the bundle.
148 argumentsBundle.putStringArray(FILTERLISTS_VERSIONS, filterListsVersions)
150 // Create a new instance of the tab fragment.
151 val aboutVersionFragment = AboutVersionFragment()
153 // Add the arguments bundle to the fragment.
154 aboutVersionFragment.arguments = argumentsBundle
156 // Return the new fragment.
157 return aboutVersionFragment
161 // Define the save about version text activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
162 private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri ->
163 // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
164 if (fileUri != null) {
165 // Initialize the file name string from the file URI last path segment.
166 var fileNameString = fileUri.lastPathSegment
168 // Query the exact file name if the API >= 26.
169 if (Build.VERSION.SDK_INT >= 26) {
170 // Get a cursor from the content resolver.
171 val contentResolverCursor = requireActivity().contentResolver.query(fileUri, null, null, null)!!
173 // Move to the first row.
174 contentResolverCursor.moveToFirst()
176 // Get the file name from the cursor.
177 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
180 contentResolverCursor.close()
184 // Get the about version string.
185 val aboutVersionString = getAboutVersionString()
187 // Open an output stream.
188 val outputStream = requireActivity().contentResolver.openOutputStream(fileUri)!!
190 // Save about version using a coroutine with Dispatchers.IO.
191 CoroutineScope(Dispatchers.Main).launch {
192 withContext(Dispatchers.IO) {
193 // Write the about version string to the output stream.
194 outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
196 // Close the output stream.
201 // Display a snackbar with the saved logcat information.
202 Snackbar.make(aboutVersionLayout, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
203 } catch (exception: Exception) {
204 // Display a snackbar with the error message.
205 Snackbar.make(aboutVersionLayout, getString(R.string.error_saving_file, fileNameString, exception.toString()), Snackbar.LENGTH_INDEFINITE).show()
210 // Define the save about version image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
211 private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri ->
212 // Save the file if the URI is not null, which happens if the user exits the file picker by pressing back.
214 SaveAboutVersionImageCoroutine.saveImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout))
217 override fun onCreate(savedInstanceState: Bundle?) {
218 // Run the default commands.
219 super.onCreate(savedInstanceState)
221 // Store the arguments in class variables.
222 filterListsVersions = requireArguments().getStringArray(FILTERLISTS_VERSIONS)!!
224 // Enable the options menu for this fragment.
225 setHasOptionsMenu(true)
228 override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
229 // 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.
230 aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
232 // Get handles for the views.
233 privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
234 versionTextView = aboutVersionLayout.findViewById(R.id.version)
235 hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
236 brandTextView = aboutVersionLayout.findViewById(R.id.brand)
237 manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
238 modelTextView = aboutVersionLayout.findViewById(R.id.model)
239 deviceTextView = aboutVersionLayout.findViewById(R.id.device)
240 bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
241 radioTextView = aboutVersionLayout.findViewById(R.id.radio)
242 softwareTextView = aboutVersionLayout.findViewById(R.id.software)
243 androidTextView = aboutVersionLayout.findViewById(R.id.android)
244 securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
245 buildTextView = aboutVersionLayout.findViewById(R.id.build)
246 kernelTextView = aboutVersionLayout.findViewById(R.id.kernel)
247 webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
248 webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
249 orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
250 i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
251 openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
252 memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
253 appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
254 appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
255 appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
256 appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
257 systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
258 systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
259 systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
260 filterListsTextView = aboutVersionLayout.findViewById(R.id.filterlists)
261 easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
262 easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
263 fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
264 fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
265 ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
266 ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
267 packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
268 certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
269 certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
270 certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
271 certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
272 certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
273 certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
274 certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
277 val version = getString(R.string.version_code, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
278 val brandLabel = getString(R.string.brand)
279 val manufacturerLabel = getString(R.string.manufacturer)
280 val modelLabel = getString(R.string.model)
281 val deviceLabel = getString(R.string.device)
282 val bootloaderLabel = getString(R.string.bootloader)
283 val androidLabel = getString(R.string.android)
284 val buildLabel = getString(R.string.build)
285 val kernelLabel = getString(R.string.kernel)
286 val webViewVersionLabel = getString(R.string.webview_version)
287 appConsumedMemoryLabel = getString(R.string.app_consumed_memory)
288 appAvailableMemoryLabel = getString(R.string.app_available_memory)
289 appTotalMemoryLabel = getString(R.string.app_total_memory)
290 appMaximumMemoryLabel = getString(R.string.app_maximum_memory)
291 systemConsumedMemoryLabel = getString(R.string.system_consumed_memory)
292 systemAvailableMemoryLabel = getString(R.string.system_available_memory)
293 systemTotalMemoryLabel = getString(R.string.system_total_memory)
294 val easyListLabel = getString(R.string.easylist_label)
295 val easyPrivacyLabel = getString(R.string.easyprivacy_label)
296 val fanboyAnnoyanceLabel = getString(R.string.fanboys_annoyance_label)
297 val fanboySocialLabel = getString(R.string.fanboys_social_label)
298 val ultraListLabel = getString(R.string.ultralist_label)
299 val ultraPrivacyLabel = getString(R.string.ultraprivacy_label)
300 val issuerDNLabel = getString(R.string.issuer_dn)
301 val subjectDNLabel = getString(R.string.subject_dn)
302 val startDateLabel = getString(R.string.start_date)
303 val endDateLabel = getString(R.string.end_date)
304 val certificateVersionLabel = getString(R.string.certificate_version)
305 val serialNumberLabel = getString(R.string.serial_number)
306 val signatureAlgorithmLabel = getString(R.string.signature_algorithm)
308 // 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.
309 // Once the minimum API >= 26 this can be accomplished with the WebView package info.
310 val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false)
311 val tabLayoutWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
312 val userAgentString = tabLayoutWebView.settings.userAgentString
314 // Get the device's information and store it in strings.
315 val brand = Build.BRAND
316 val manufacturer = Build.MANUFACTURER
317 val model = Build.MODEL
318 val device = Build.DEVICE
319 val bootloader = Build.BOOTLOADER
320 val radio = Build.getRadioVersion()
321 val android = getString(R.string.api, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)
322 val build = Build.DISPLAY
323 val kernel = System.getProperty("os.version")
325 // Get the WebView version, selecting the substring that begins after `Chrome/` and goes until the next ` `.
326 val webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")))
328 // Get the Orbot version name if Orbot is installed.
329 val orbot: String = try {
330 // Store the version name.
331 requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
332 } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed.
333 // Store an empty string.
337 // Get the I2P version name if I2P is installed.
338 val i2p: String = try {
339 // Check to see if the F-Droid flavor is installed.
340 requireContext().getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
341 } catch (exception: PackageManager.NameNotFoundException) { // The F-Droid flavor is not installed.
343 // Check to see if the F-Droid flavor is installed.
344 requireContext().getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
345 } catch (exception: PackageManager.NameNotFoundException) { // The Google Play flavor is not installed either.
346 // Store an empty string.
351 // Get the OpenKeychain version name if it is installed.
352 val openKeychain: String = try {
353 // Store the version name.
354 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
355 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
356 // Store an empty string.
360 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
361 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
362 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
363 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
364 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
365 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
366 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
367 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
368 val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
369 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
370 val easyListStringBuilder = SpannableStringBuilder(easyListLabel + filterListsVersions[0])
371 val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + filterListsVersions[1])
372 val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + filterListsVersions[2])
373 val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + filterListsVersions[3])
374 val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + filterListsVersions[4])
375 val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + filterListsVersions[5])
377 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
378 blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
380 // Set the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
381 brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
382 manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
383 modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
384 deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
385 bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
386 androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
387 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
388 kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
391 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
393 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
394 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
395 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
397 // Display the strings in the text boxes.
398 versionTextView.text = version
399 brandTextView.text = brandStringBuilder
400 manufacturerTextView.text = manufacturerStringBuilder
401 modelTextView.text = modelStringBuilder
402 deviceTextView.text = deviceStringBuilder
403 bootloaderTextView.text = bootloaderStringBuilder
404 androidTextView.text = androidStringBuilder
405 buildTextView.text = buildStringBuilder
406 kernelTextView.text = kernelStringBuilder
407 webViewVersionTextView.text = webViewVersionStringBuilder
408 easyListTextView.text = easyListStringBuilder
409 easyPrivacyTextView.text = easyPrivacyStringBuilder
410 fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
411 fanboySocialTextView.text = fanboySocialStringBuilder
412 ultraListTextView.text = ultraListStringBuilder
413 ultraPrivacyTextView.text = ultraPrivacyStringBuilder
415 // Only populate the radio text view if there is a radio in the device.
416 // 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>
417 if (radio != null && radio.isNotEmpty()) {
419 val radioLabel = getString(R.string.radio)
421 // Create a spannable string builder.
422 val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
424 // Set the span to display the radio in blue.
425 radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
427 // Display the string in the text view.
428 radioTextView.text = radioStringBuilder
429 } else { // This device does not have a radio.
430 // Hide the radio text view.
431 radioTextView.visibility = View.GONE
435 val securityPatchLabel = getString(R.string.security_patch)
437 // Get the security patch version.
438 val securityPatch = Build.VERSION.SECURITY_PATCH
440 // Create a spannable string builder.
441 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
443 // Set the span to display the security patch version in blue.
444 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
446 // Display the string in the text view.
447 securityPatchTextView.text = securityPatchStringBuilder
449 // Create the WebView provider label.
450 val webViewProviderLabel = getString(R.string.webview_provider)
452 // Get the current WebView package info.
453 val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
455 // Get the WebView provider name.
456 val webViewPackageName = webViewPackageInfo.packageName
458 // Create the spannable string builder.
459 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
461 // Apply the coloration.
462 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
464 // Display the WebView provider.
465 webViewProviderTextView.text = webViewProviderStringBuilder
467 // Only populate the Orbot text view if it is installed.
468 if (orbot.isNotEmpty()) {
470 val orbotLabel = getString(R.string.orbot)
472 // Create a spannable string builder.
473 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
475 // Set the span to display the Orbot version.
476 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
478 // Display the string in the text view.
479 orbotTextView.text = orbotStringBuilder
480 } else { // Orbot is not installed.
481 // Hide the Orbot text view.
482 orbotTextView.visibility = View.GONE
485 // Only populate the I2P text view if it is installed.
486 if (i2p.isNotEmpty()) {
488 val i2pLabel = getString(R.string.i2p)
490 // Create a spannable string builder.
491 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
493 // Set the span to display the I2P version.
494 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
496 // Display the string in the text view.
497 i2pTextView.text = i2pStringBuilder
498 } else { // I2P is not installed.
499 // Hide the I2P text view.
500 i2pTextView.visibility = View.GONE
503 // Only populate the OpenKeychain text view if it is installed.
504 if (openKeychain.isNotEmpty()) {
506 val openKeychainLabel = getString(R.string.openkeychain)
508 // Create a spannable string builder.
509 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
511 // Set the span to display the OpenKeychain version.
512 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
514 // Display the string in the text view.
515 openKeychainTextView.text = openKeychainStringBuilder
516 } else { //OpenKeychain is not installed.
517 // Hide the OpenKeychain text view.
518 openKeychainTextView.visibility = View.GONE
521 // Display the package signature.
523 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
524 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
525 @Suppress("DEPRECATION")
526 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
529 // Convert the signature to a byte array input stream.
530 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
532 // Display the certificate information on the screen.
534 // Instantiate a certificate factory.
535 val certificateFactory = CertificateFactory.getInstance("X509")
537 // Generate an X509 certificate.
538 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
540 // Store the individual sections of the certificate.
541 val issuerDNPrincipal = x509Certificate.issuerDN
542 val subjectDNPrincipal = x509Certificate.subjectDN
543 val startDate = x509Certificate.notBefore
544 val endDate = x509Certificate.notAfter
545 val certificateVersion = x509Certificate.version
546 val serialNumberBigInteger = x509Certificate.serialNumber
547 val signatureAlgorithmNameString = x509Certificate.sigAlgName
549 // Create a spannable string builder for each text view that needs multiple colors of text.
550 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
551 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
552 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
553 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
554 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
555 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
556 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
558 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
559 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
560 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
561 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
562 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
563 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
564 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
565 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
567 // Display the strings in the text boxes.
568 certificateIssuerDnTextView.text = issuerDNStringBuilder
569 certificateSubjectDnTextView.text = subjectDNStringBuilder
570 certificateStartDateTextView.text = startDateStringBuilder
571 certificateEndDateTextView.text = endDataStringBuilder
572 certificateVersionTextView.text = certificateVersionStringBuilder
573 certificateSerialNumberTextView.text = serialNumberStringBuilder
574 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
575 } catch (certificateException: CertificateException) {
576 // Do nothing if there is a certificate error.
579 // Get a handle for the runtime.
580 runtime = Runtime.getRuntime()
582 // Get a handle for the activity manager.
583 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
585 // Instantiate a memory info variable.
586 memoryInfo = ActivityManager.MemoryInfo()
588 // Define a number format.
589 numberFormat = NumberFormat.getInstance()
591 // Set the minimum and maximum number of fraction digits.
592 numberFormat.minimumFractionDigits = 2
593 numberFormat.maximumFractionDigits = 2
595 // Update the memory usage.
596 updateMemoryUsage(requireActivity())
597 } catch (e: PackageManager.NameNotFoundException) {
598 // Do nothing if the package manager says Privacy Browser isn't installed.
601 // Scroll the tab if the saved instance state is not null.
602 if (savedInstanceState != null) {
603 aboutVersionLayout.post {
604 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
605 aboutVersionLayout.scrollY = savedInstanceState.getInt("scroll_y")
609 // Return the tab layout.
610 return aboutVersionLayout
613 override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
614 // Inflate the about version menu.
615 menuInflater.inflate(R.menu.about_version_options_menu, menu)
617 // Run the default commands.
618 super.onCreateOptionsMenu(menu, menuInflater)
621 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
622 // Run the appropriate commands.
623 when (menuItem.itemId) {
624 R.id.copy -> { // Copy.
625 // Get the about version string.
626 val aboutVersionString = getAboutVersionString()
628 // Get a handle for the clipboard manager.
629 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
631 // Place the about version string in a clip data.
632 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
634 // Place the clip data on the clipboard.
635 clipboardManager.setPrimaryClip(aboutVersionClipData)
637 // Display a snackbar if the API <= 32 (Android 12L). Beginning in Android 13 the OS displays a notification that covers up the snackbar.
638 if (Build.VERSION.SDK_INT <= 32)
639 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
641 // Consume the event.
645 R.id.share -> { // Share.
646 // Get the about version string.
647 val aboutString = getAboutVersionString()
649 // Create a share intent.
650 val shareIntent = Intent(Intent.ACTION_SEND)
652 // Add the about version string to the intent.
653 shareIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
655 // Set the MIME type.
656 shareIntent.type = "text/plain"
658 // Set the intent to open in a new task.
659 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
662 startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
664 // Consume the event.
668 R.id.save_text -> { // Save text.
669 // Open the file picker.
670 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
672 // Consume the event.
676 R.id.save_image -> { // Save image.
677 // Open the file picker.
678 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
680 // Consume the event.
684 else -> { // The home button was selected.
685 // Run the parents class on return.
686 return super.onOptionsItemSelected(menuItem)
691 override fun onSaveInstanceState(savedInstanceState: Bundle) {
692 // Run the default commands.
693 super.onSaveInstanceState(savedInstanceState)
695 // Save the scroll positions.
696 savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
697 savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
700 override fun onPause() {
701 // Run the default commands.
704 // Pause the updating of the memory usage.
705 updateMemoryUsageBoolean = false
708 override fun onResume() {
709 // Run the default commands.
712 // Resume the updating of the memory usage.
713 updateMemoryUsageBoolean = true
716 private fun updateMemoryUsage(activity: Activity) {
718 // Update the memory usage if enabled.
719 if (updateMemoryUsageBoolean) {
720 // Populate the memory info variable.
721 activityManager.getMemoryInfo(memoryInfo)
723 // Get the app memory information.
724 val appAvailableMemoryLong = runtime.freeMemory()
725 val appTotalMemoryLong = runtime.totalMemory()
726 val appMaximumMemoryLong = runtime.maxMemory()
728 // Calculate the app consumed memory.
729 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
731 // Get the system memory information.
732 val systemTotalMemoryLong = memoryInfo.totalMem
733 val systemAvailableMemoryLong = memoryInfo.availMem
735 // Calculate the system consumed memory.
736 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
738 // Convert the memory information into mebibytes.
739 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
740 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
741 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
742 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
743 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
744 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
745 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
747 // Get the mebibyte string.
748 val mebibyte = getString(R.string.mebibyte)
750 // Calculate the mebibyte length.
751 val mebibyteLength = mebibyte.length
753 // Create spannable string builders.
754 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
755 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
756 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
757 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
758 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
759 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
760 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
762 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
763 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
764 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
765 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
766 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
767 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
768 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
769 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
771 // Display the string in the text boxes.
772 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
773 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
774 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
775 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
776 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
777 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
778 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
781 // Schedule another memory update if the activity has not been destroyed.
782 if (!activity.isDestroyed) {
783 // Create a handler to update the memory usage.
784 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
786 // Create a runnable to update the memory usage.
787 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
789 // Update the memory usage after 1000 milliseconds
790 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
792 } catch (exception: Exception) {
797 private fun getAboutVersionString(): String {
798 // Initialize an about version string builder.
799 val aboutVersionStringBuilder = StringBuilder()
801 // Populate the about version string builder.
802 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
803 aboutVersionStringBuilder.append("\n")
804 aboutVersionStringBuilder.append(versionTextView.text)
805 aboutVersionStringBuilder.append("\n\n")
806 aboutVersionStringBuilder.append(hardwareTextView.text)
807 aboutVersionStringBuilder.append("\n")
808 aboutVersionStringBuilder.append(brandTextView.text)
809 aboutVersionStringBuilder.append("\n")
810 aboutVersionStringBuilder.append(manufacturerTextView.text)
811 aboutVersionStringBuilder.append("\n")
812 aboutVersionStringBuilder.append(modelTextView.text)
813 aboutVersionStringBuilder.append("\n")
814 aboutVersionStringBuilder.append(deviceTextView.text)
815 aboutVersionStringBuilder.append("\n")
816 aboutVersionStringBuilder.append(bootloaderTextView.text)
817 aboutVersionStringBuilder.append("\n")
818 if (radioTextView.visibility == View.VISIBLE) {
819 aboutVersionStringBuilder.append(radioTextView.text)
820 aboutVersionStringBuilder.append("\n")
822 aboutVersionStringBuilder.append("\n")
823 aboutVersionStringBuilder.append(softwareTextView.text)
824 aboutVersionStringBuilder.append("\n")
825 aboutVersionStringBuilder.append(androidTextView.text)
826 aboutVersionStringBuilder.append("\n")
827 if (securityPatchTextView.visibility == View.VISIBLE) {
828 aboutVersionStringBuilder.append(securityPatchTextView.text)
829 aboutVersionStringBuilder.append("\n")
831 aboutVersionStringBuilder.append(buildTextView.text)
832 aboutVersionStringBuilder.append("\n")
833 aboutVersionStringBuilder.append(kernelTextView.text)
834 aboutVersionStringBuilder.append("\n")
835 if (webViewProviderTextView.visibility == View.VISIBLE) {
836 aboutVersionStringBuilder.append(webViewProviderTextView.text)
837 aboutVersionStringBuilder.append("\n")
839 aboutVersionStringBuilder.append(webViewVersionTextView.text)
840 aboutVersionStringBuilder.append("\n")
841 if (orbotTextView.visibility == View.VISIBLE) {
842 aboutVersionStringBuilder.append(orbotTextView.text)
843 aboutVersionStringBuilder.append("\n")
845 if (i2pTextView.visibility == View.VISIBLE) {
846 aboutVersionStringBuilder.append(i2pTextView.text)
847 aboutVersionStringBuilder.append("\n")
849 if (openKeychainTextView.visibility == View.VISIBLE) {
850 aboutVersionStringBuilder.append(openKeychainTextView.text)
851 aboutVersionStringBuilder.append("\n")
853 aboutVersionStringBuilder.append("\n")
854 aboutVersionStringBuilder.append(memoryUsageTextView.text)
855 aboutVersionStringBuilder.append("\n")
856 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
857 aboutVersionStringBuilder.append("\n")
858 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
859 aboutVersionStringBuilder.append("\n")
860 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
861 aboutVersionStringBuilder.append("\n")
862 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
863 aboutVersionStringBuilder.append("\n")
864 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
865 aboutVersionStringBuilder.append("\n")
866 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
867 aboutVersionStringBuilder.append("\n")
868 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
869 aboutVersionStringBuilder.append("\n\n")
870 aboutVersionStringBuilder.append(filterListsTextView.text)
871 aboutVersionStringBuilder.append("\n")
872 aboutVersionStringBuilder.append(easyListTextView.text)
873 aboutVersionStringBuilder.append("\n")
874 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
875 aboutVersionStringBuilder.append("\n")
876 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
877 aboutVersionStringBuilder.append("\n")
878 aboutVersionStringBuilder.append(fanboySocialTextView.text)
879 aboutVersionStringBuilder.append("\n")
880 aboutVersionStringBuilder.append(ultraListTextView.text)
881 aboutVersionStringBuilder.append("\n")
882 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
883 aboutVersionStringBuilder.append("\n\n")
884 aboutVersionStringBuilder.append(packageSignatureTextView.text)
885 aboutVersionStringBuilder.append("\n")
886 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
887 aboutVersionStringBuilder.append("\n")
888 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
889 aboutVersionStringBuilder.append("\n")
890 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
891 aboutVersionStringBuilder.append("\n")
892 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
893 aboutVersionStringBuilder.append("\n")
894 aboutVersionStringBuilder.append(certificateVersionTextView.text)
895 aboutVersionStringBuilder.append("\n")
896 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
897 aboutVersionStringBuilder.append("\n")
898 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
900 // Return the string.
901 return aboutVersionStringBuilder.toString()