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.widget.TextView
46 import androidx.activity.result.contract.ActivityResultContracts
47 import androidx.fragment.app.Fragment
48 import androidx.webkit.WebViewCompat
50 import com.google.android.material.snackbar.Snackbar
52 import com.stoutner.privacybrowser.BuildConfig
53 import com.stoutner.privacybrowser.R
54 import com.stoutner.privacybrowser.coroutines.SaveAboutVersionImageCoroutine
56 import kotlinx.coroutines.CoroutineScope
57 import kotlinx.coroutines.Dispatchers
58 import kotlinx.coroutines.launch
59 import kotlinx.coroutines.withContext
61 import java.io.ByteArrayInputStream
62 import java.io.InputStream
63 import java.lang.Exception
64 import java.nio.charset.StandardCharsets
65 import java.security.cert.CertificateException
66 import java.security.cert.CertificateFactory
67 import java.security.cert.X509Certificate
68 import java.text.DateFormat
69 import java.text.NumberFormat
71 import kotlin.text.StringBuilder
73 // Define the class constants.
74 private const val FILTERLISTS_VERSIONS = "A"
75 private const val SCROLL_Y = "B"
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 securityPatchLabel = getString(R.string.security_patch)
285 val buildLabel = getString(R.string.build)
286 val kernelLabel = getString(R.string.kernel)
287 val webViewProviderLabel = getString(R.string.webview_provider)
288 val webViewVersionLabel = getString(R.string.webview_version)
289 appConsumedMemoryLabel = getString(R.string.app_consumed_memory)
290 appAvailableMemoryLabel = getString(R.string.app_available_memory)
291 appTotalMemoryLabel = getString(R.string.app_total_memory)
292 appMaximumMemoryLabel = getString(R.string.app_maximum_memory)
293 systemConsumedMemoryLabel = getString(R.string.system_consumed_memory)
294 systemAvailableMemoryLabel = getString(R.string.system_available_memory)
295 systemTotalMemoryLabel = getString(R.string.system_total_memory)
296 val easyListLabel = getString(R.string.easylist_label)
297 val easyPrivacyLabel = getString(R.string.easyprivacy_label)
298 val fanboyAnnoyanceLabel = getString(R.string.fanboys_annoyance_label)
299 val fanboySocialLabel = getString(R.string.fanboys_social_label)
300 val ultraListLabel = getString(R.string.ultralist_label)
301 val ultraPrivacyLabel = getString(R.string.ultraprivacy_label)
302 val issuerDNLabel = getString(R.string.issuer_dn)
303 val subjectDNLabel = getString(R.string.subject_dn)
304 val startDateLabel = getString(R.string.start_date)
305 val endDateLabel = getString(R.string.end_date)
306 val certificateVersionLabel = getString(R.string.certificate_version)
307 val serialNumberLabel = getString(R.string.serial_number)
308 val signatureAlgorithmLabel = getString(R.string.signature_algorithm)
310 // Get the current WebView package info. This can be replaced by the direct call once the minimum API >= 26. <https://developer.android.com/reference/android/webkit/WebView#getCurrentWebViewPackage()>
311 val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
313 // Get the device's information and store it in strings.
314 val brand = Build.BRAND
315 val manufacturer = Build.MANUFACTURER
316 val model = Build.MODEL
317 val device = Build.DEVICE
318 val bootloader = Build.BOOTLOADER
319 val radio = Build.getRadioVersion()
320 val android = getString(R.string.api, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)
321 val securityPatch = Build.VERSION.SECURITY_PATCH
322 val build = Build.DISPLAY
323 val kernel = System.getProperty("os.version")
324 val webViewPackageName = webViewPackageInfo.packageName
325 val webViewVersion = webViewPackageInfo.versionName
327 // Get the Orbot version name if Orbot is installed.
328 val orbot: String = try {
329 // Store the version name.
330 requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
331 } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed.
332 // Store an empty string.
336 // Get the I2P version name if I2P is installed.
337 val i2p: String = try {
338 // Check to see if the F-Droid flavor is installed.
339 getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
340 } catch (exception: PackageManager.NameNotFoundException) { // The F-Droid flavor is not installed.
342 // Check to see if the F-Droid flavor is installed.
343 getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
344 } catch (exception: PackageManager.NameNotFoundException) { // The Google Play flavor is not installed either.
345 // Store an empty string.
350 // Get the OpenKeychain version name if it is installed.
351 val openKeychain: String = try {
352 // Store the version name.
353 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
354 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
355 // Store an empty string.
359 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
360 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
361 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
362 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
363 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
364 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
365 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
366 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
367 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
368 val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
369 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
370 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webViewVersion)
371 val easyListStringBuilder = SpannableStringBuilder(easyListLabel + filterListsVersions[0])
372 val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + filterListsVersions[1])
373 val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + filterListsVersions[2])
374 val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + filterListsVersions[3])
375 val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + filterListsVersions[4])
376 val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + filterListsVersions[5])
378 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
379 blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
381 // Set the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
382 brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
383 manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
384 modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
385 deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
386 bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
387 androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
388 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390 kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
391 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
393 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
394 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
395 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
396 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
397 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
398 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
400 // Display the strings in the text boxes.
401 versionTextView.text = version
402 brandTextView.text = brandStringBuilder
403 manufacturerTextView.text = manufacturerStringBuilder
404 modelTextView.text = modelStringBuilder
405 deviceTextView.text = deviceStringBuilder
406 bootloaderTextView.text = bootloaderStringBuilder
407 androidTextView.text = androidStringBuilder
408 securityPatchTextView.text = securityPatchStringBuilder
409 buildTextView.text = buildStringBuilder
410 kernelTextView.text = kernelStringBuilder
411 webViewProviderTextView.text = webViewProviderStringBuilder
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
439 // Only populate the Orbot text view if it is installed.
440 if (orbot.isNotEmpty()) {
442 val orbotLabel = getString(R.string.orbot)
444 // Create a spannable string builder.
445 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
447 // Set the span to display the Orbot version.
448 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
450 // Display the string in the text view.
451 orbotTextView.text = orbotStringBuilder
452 } else { // Orbot is not installed.
453 // Hide the Orbot text view.
454 orbotTextView.visibility = View.GONE
457 // Only populate the I2P text view if it is installed.
458 if (i2p.isNotEmpty()) {
460 val i2pLabel = getString(R.string.i2p)
462 // Create a spannable string builder.
463 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
465 // Set the span to display the I2P version.
466 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
468 // Display the string in the text view.
469 i2pTextView.text = i2pStringBuilder
470 } else { // I2P is not installed.
471 // Hide the I2P text view.
472 i2pTextView.visibility = View.GONE
475 // Only populate the OpenKeychain text view if it is installed.
476 if (openKeychain.isNotEmpty()) {
478 val openKeychainLabel = getString(R.string.openkeychain)
480 // Create a spannable string builder.
481 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
483 // Set the span to display the OpenKeychain version.
484 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
486 // Display the string in the text view.
487 openKeychainTextView.text = openKeychainStringBuilder
488 } else { //OpenKeychain is not installed.
489 // Hide the OpenKeychain text view.
490 openKeychainTextView.visibility = View.GONE
493 // Display the package signature.
495 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
496 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
497 @Suppress("DEPRECATION")
498 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
501 // Convert the signature to a byte array input stream.
502 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
504 // Display the certificate information on the screen.
506 // Instantiate a certificate factory.
507 val certificateFactory = CertificateFactory.getInstance("X509")
509 // Generate an X509 certificate.
510 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
512 // Store the individual sections of the certificate.
513 val issuerDNPrincipal = x509Certificate.issuerDN
514 val subjectDNPrincipal = x509Certificate.subjectDN
515 val startDate = x509Certificate.notBefore
516 val endDate = x509Certificate.notAfter
517 val certificateVersion = x509Certificate.version
518 val serialNumberBigInteger = x509Certificate.serialNumber
519 val signatureAlgorithmNameString = x509Certificate.sigAlgName
521 // Create a spannable string builder for each text view that needs multiple colors of text.
522 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
523 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
524 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
525 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
526 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
527 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
528 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
530 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
531 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
532 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
533 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
534 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
535 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
536 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
537 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
539 // Display the strings in the text boxes.
540 certificateIssuerDnTextView.text = issuerDNStringBuilder
541 certificateSubjectDnTextView.text = subjectDNStringBuilder
542 certificateStartDateTextView.text = startDateStringBuilder
543 certificateEndDateTextView.text = endDataStringBuilder
544 certificateVersionTextView.text = certificateVersionStringBuilder
545 certificateSerialNumberTextView.text = serialNumberStringBuilder
546 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
547 } catch (certificateException: CertificateException) {
548 // Do nothing if there is a certificate error.
551 // Get a handle for the runtime.
552 runtime = Runtime.getRuntime()
554 // Get a handle for the activity manager.
555 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
557 // Instantiate a memory info variable.
558 memoryInfo = ActivityManager.MemoryInfo()
560 // Define a number format.
561 numberFormat = NumberFormat.getInstance()
563 // Set the minimum and maximum number of fraction digits.
564 numberFormat.minimumFractionDigits = 2
565 numberFormat.maximumFractionDigits = 2
567 // Update the memory usage.
568 updateMemoryUsage(requireActivity())
569 } catch (e: PackageManager.NameNotFoundException) {
570 // Do nothing if the package manager says Privacy Browser isn't installed.
573 // Scroll the tab if the saved instance state is not null.
574 if (savedInstanceState != null) {
575 aboutVersionLayout.post {
576 aboutVersionLayout.scrollY = savedInstanceState.getInt(SCROLL_Y)
580 // Return the tab layout.
581 return aboutVersionLayout
584 override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
585 // Inflate the about version menu.
586 menuInflater.inflate(R.menu.about_version_options_menu, menu)
588 // Run the default commands.
589 super.onCreateOptionsMenu(menu, menuInflater)
592 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
593 // Run the appropriate commands.
594 when (menuItem.itemId) {
595 R.id.copy -> { // Copy.
596 // Get the about version string.
597 val aboutVersionString = getAboutVersionString()
599 // Get a handle for the clipboard manager.
600 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
602 // Place the about version string in a clip data.
603 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
605 // Place the clip data on the clipboard.
606 clipboardManager.setPrimaryClip(aboutVersionClipData)
608 // Display a snackbar if the API <= 32 (Android 12L). Beginning in Android 13 the OS displays a notification that covers up the snackbar.
609 if (Build.VERSION.SDK_INT <= 32)
610 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
612 // Consume the event.
616 R.id.share -> { // Share.
617 // Get the about version string.
618 val aboutString = getAboutVersionString()
620 // Create a share intent.
621 val shareIntent = Intent(Intent.ACTION_SEND)
623 // Add the about version string to the intent.
624 shareIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
626 // Set the MIME type.
627 shareIntent.type = "text/plain"
629 // Set the intent to open in a new task.
630 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
633 startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
635 // Consume the event.
639 R.id.save_text -> { // Save text.
640 // Open the file picker.
641 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
643 // Consume the event.
647 R.id.save_image -> { // Save image.
648 // Open the file picker.
649 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
651 // Consume the event.
655 else -> { // The home button was selected.
656 // Run the parents class on return.
657 return super.onOptionsItemSelected(menuItem)
662 override fun onSaveInstanceState(savedInstanceState: Bundle) {
663 // Run the default commands.
664 super.onSaveInstanceState(savedInstanceState)
666 // Save the scroll position.
667 savedInstanceState.putInt(SCROLL_Y, aboutVersionLayout.scrollY)
670 override fun onPause() {
671 // Run the default commands.
674 // Pause the updating of the memory usage.
675 updateMemoryUsageBoolean = false
678 override fun onResume() {
679 // Run the default commands.
682 // Resume the updating of the memory usage.
683 updateMemoryUsageBoolean = true
686 private fun updateMemoryUsage(activity: Activity) {
688 // Update the memory usage if enabled.
689 if (updateMemoryUsageBoolean) {
690 // Populate the memory info variable.
691 activityManager.getMemoryInfo(memoryInfo)
693 // Get the app memory information.
694 val appAvailableMemoryLong = runtime.freeMemory()
695 val appTotalMemoryLong = runtime.totalMemory()
696 val appMaximumMemoryLong = runtime.maxMemory()
698 // Calculate the app consumed memory.
699 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
701 // Get the system memory information.
702 val systemTotalMemoryLong = memoryInfo.totalMem
703 val systemAvailableMemoryLong = memoryInfo.availMem
705 // Calculate the system consumed memory.
706 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
708 // Convert the memory information into mebibytes.
709 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
710 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
711 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
712 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
713 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
714 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
715 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
717 // Get the mebibyte string.
718 val mebibyte = getString(R.string.mebibyte)
720 // Calculate the mebibyte length.
721 val mebibyteLength = mebibyte.length
723 // Create spannable string builders.
724 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
725 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
726 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
727 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
728 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
729 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
730 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
732 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
733 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
734 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
735 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
736 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
737 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
738 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
739 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
741 // Display the string in the text boxes.
742 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
743 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
744 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
745 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
746 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
747 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
748 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
751 // Schedule another memory update if the activity has not been destroyed.
752 if (!activity.isDestroyed) {
753 // Create a handler to update the memory usage.
754 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
756 // Create a runnable to update the memory usage.
757 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
759 // Update the memory usage after 1000 milliseconds
760 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
762 } catch (exception: Exception) {
767 private fun getAboutVersionString(): String {
768 // Initialize an about version string builder.
769 val aboutVersionStringBuilder = StringBuilder()
771 // Populate the about version string builder.
772 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
773 aboutVersionStringBuilder.append("\n")
774 aboutVersionStringBuilder.append(versionTextView.text)
775 aboutVersionStringBuilder.append("\n\n")
776 aboutVersionStringBuilder.append(hardwareTextView.text)
777 aboutVersionStringBuilder.append("\n")
778 aboutVersionStringBuilder.append(brandTextView.text)
779 aboutVersionStringBuilder.append("\n")
780 aboutVersionStringBuilder.append(manufacturerTextView.text)
781 aboutVersionStringBuilder.append("\n")
782 aboutVersionStringBuilder.append(modelTextView.text)
783 aboutVersionStringBuilder.append("\n")
784 aboutVersionStringBuilder.append(deviceTextView.text)
785 aboutVersionStringBuilder.append("\n")
786 aboutVersionStringBuilder.append(bootloaderTextView.text)
787 aboutVersionStringBuilder.append("\n")
788 if (radioTextView.visibility == View.VISIBLE) {
789 aboutVersionStringBuilder.append(radioTextView.text)
790 aboutVersionStringBuilder.append("\n")
792 aboutVersionStringBuilder.append("\n")
793 aboutVersionStringBuilder.append(softwareTextView.text)
794 aboutVersionStringBuilder.append("\n")
795 aboutVersionStringBuilder.append(androidTextView.text)
796 aboutVersionStringBuilder.append("\n")
797 if (securityPatchTextView.visibility == View.VISIBLE) {
798 aboutVersionStringBuilder.append(securityPatchTextView.text)
799 aboutVersionStringBuilder.append("\n")
801 aboutVersionStringBuilder.append(buildTextView.text)
802 aboutVersionStringBuilder.append("\n")
803 aboutVersionStringBuilder.append(kernelTextView.text)
804 aboutVersionStringBuilder.append("\n")
805 if (webViewProviderTextView.visibility == View.VISIBLE) {
806 aboutVersionStringBuilder.append(webViewProviderTextView.text)
807 aboutVersionStringBuilder.append("\n")
809 aboutVersionStringBuilder.append(webViewVersionTextView.text)
810 aboutVersionStringBuilder.append("\n")
811 if (orbotTextView.visibility == View.VISIBLE) {
812 aboutVersionStringBuilder.append(orbotTextView.text)
813 aboutVersionStringBuilder.append("\n")
815 if (i2pTextView.visibility == View.VISIBLE) {
816 aboutVersionStringBuilder.append(i2pTextView.text)
817 aboutVersionStringBuilder.append("\n")
819 if (openKeychainTextView.visibility == View.VISIBLE) {
820 aboutVersionStringBuilder.append(openKeychainTextView.text)
821 aboutVersionStringBuilder.append("\n")
823 aboutVersionStringBuilder.append("\n")
824 aboutVersionStringBuilder.append(memoryUsageTextView.text)
825 aboutVersionStringBuilder.append("\n")
826 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
827 aboutVersionStringBuilder.append("\n")
828 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
829 aboutVersionStringBuilder.append("\n")
830 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
831 aboutVersionStringBuilder.append("\n")
832 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
833 aboutVersionStringBuilder.append("\n")
834 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
835 aboutVersionStringBuilder.append("\n")
836 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
837 aboutVersionStringBuilder.append("\n")
838 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
839 aboutVersionStringBuilder.append("\n\n")
840 aboutVersionStringBuilder.append(filterListsTextView.text)
841 aboutVersionStringBuilder.append("\n")
842 aboutVersionStringBuilder.append(easyListTextView.text)
843 aboutVersionStringBuilder.append("\n")
844 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
845 aboutVersionStringBuilder.append("\n")
846 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
847 aboutVersionStringBuilder.append("\n")
848 aboutVersionStringBuilder.append(fanboySocialTextView.text)
849 aboutVersionStringBuilder.append("\n")
850 aboutVersionStringBuilder.append(ultraListTextView.text)
851 aboutVersionStringBuilder.append("\n")
852 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
853 aboutVersionStringBuilder.append("\n\n")
854 aboutVersionStringBuilder.append(packageSignatureTextView.text)
855 aboutVersionStringBuilder.append("\n")
856 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
857 aboutVersionStringBuilder.append("\n")
858 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
859 aboutVersionStringBuilder.append("\n")
860 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
861 aboutVersionStringBuilder.append("\n")
862 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
863 aboutVersionStringBuilder.append("\n")
864 aboutVersionStringBuilder.append(certificateVersionTextView.text)
865 aboutVersionStringBuilder.append("\n")
866 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
867 aboutVersionStringBuilder.append("\n")
868 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
870 // Return the string.
871 return aboutVersionStringBuilder.toString()