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 = "filterlists_versions"
75 private const val MEBIBYTE = 1048576
77 class AboutVersionFragment : Fragment() {
78 // Define the class variables.
79 private var updateMemoryUsageBoolean = true
81 // Declare the class variables.
82 private lateinit var aboutVersionLayout: View
83 private lateinit var activityManager: ActivityManager
84 private lateinit var appAvailableMemoryLabel: String
85 private lateinit var appConsumedMemoryLabel: String
86 private lateinit var appMaximumMemoryLabel: String
87 private lateinit var appTotalMemoryLabel: String
88 private lateinit var blueColorSpan: ForegroundColorSpan
89 private lateinit var filterListsVersions: Array<String>
90 private lateinit var memoryInfo: ActivityManager.MemoryInfo
91 private lateinit var numberFormat: NumberFormat
92 private lateinit var runtime: Runtime
93 private lateinit var systemAvailableMemoryLabel: String
94 private lateinit var systemConsumedMemoryLabel: String
95 private lateinit var systemTotalMemoryLabel: String
97 // Declare the class views.
98 private lateinit var androidTextView: TextView
99 private lateinit var appAvailableMemoryTextView: TextView
100 private lateinit var appConsumedMemoryTextView: TextView
101 private lateinit var appMaximumMemoryTextView: TextView
102 private lateinit var appTotalMemoryTextView: TextView
103 private lateinit var brandTextView: TextView
104 private lateinit var bootloaderTextView: TextView
105 private lateinit var certificateEndDateTextView: TextView
106 private lateinit var certificateIssuerDnTextView: TextView
107 private lateinit var certificateSerialNumberTextView: TextView
108 private lateinit var certificateSignatureAlgorithmTextView: TextView
109 private lateinit var certificateStartDateTextView: TextView
110 private lateinit var certificateSubjectDnTextView: TextView
111 private lateinit var certificateVersionTextView: TextView
112 private lateinit var buildTextView: TextView
113 private lateinit var deviceTextView: TextView
114 private lateinit var easyListTextView: TextView
115 private lateinit var easyPrivacyTextView: TextView
116 private lateinit var fanboyAnnoyanceTextView: TextView
117 private lateinit var fanboySocialTextView: TextView
118 private lateinit var filterListsTextView: TextView
119 private lateinit var hardwareTextView: TextView
120 private lateinit var i2pTextView: TextView
121 private lateinit var kernelTextView: TextView
122 private lateinit var manufacturerTextView: TextView
123 private lateinit var memoryUsageTextView: TextView
124 private lateinit var modelTextView: TextView
125 private lateinit var openKeychainTextView: TextView
126 private lateinit var orbotTextView: TextView
127 private lateinit var packageSignatureTextView: TextView
128 private lateinit var privacyBrowserTextView: TextView
129 private lateinit var radioTextView: TextView
130 private lateinit var securityPatchTextView: TextView
131 private lateinit var softwareTextView: TextView
132 private lateinit var systemAvailableMemoryTextView: TextView
133 private lateinit var systemConsumedMemoryTextView: TextView
134 private lateinit var systemTotalMemoryTextView: TextView
135 private lateinit var versionTextView: TextView
136 private lateinit var ultraListTextView: TextView
137 private lateinit var ultraPrivacyTextView: TextView
138 private lateinit var webViewProviderTextView: TextView
139 private lateinit var webViewVersionTextView: TextView
142 fun createTab(filterListsVersions: Array<String>): AboutVersionFragment {
143 // Create an arguments bundle.
144 val argumentsBundle = Bundle()
146 // Store the arguments in the bundle.
147 argumentsBundle.putStringArray(FILTERLISTS_VERSIONS, filterListsVersions)
149 // Create a new instance of the tab fragment.
150 val aboutVersionFragment = AboutVersionFragment()
152 // Add the arguments bundle to the fragment.
153 aboutVersionFragment.arguments = argumentsBundle
155 // Return the new fragment.
156 return aboutVersionFragment
160 // Define the save about version text activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
161 private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri ->
162 // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
163 if (fileUri != null) {
164 // Initialize the file name string from the file URI last path segment.
165 var fileNameString = fileUri.lastPathSegment
167 // Query the exact file name if the API >= 26.
168 if (Build.VERSION.SDK_INT >= 26) {
169 // Get a cursor from the content resolver.
170 val contentResolverCursor = requireActivity().contentResolver.query(fileUri, null, null, null)!!
172 // Move to the first row.
173 contentResolverCursor.moveToFirst()
175 // Get the file name from the cursor.
176 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
179 contentResolverCursor.close()
183 // Get the about version string.
184 val aboutVersionString = getAboutVersionString()
186 // Open an output stream.
187 val outputStream = requireActivity().contentResolver.openOutputStream(fileUri)!!
189 // Save about version using a coroutine with Dispatchers.IO.
190 CoroutineScope(Dispatchers.Main).launch {
191 withContext(Dispatchers.IO) {
192 // Write the about version string to the output stream.
193 outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
195 // Close the output stream.
200 // Display a snackbar with the saved logcat information.
201 Snackbar.make(aboutVersionLayout, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
202 } catch (exception: Exception) {
203 // Display a snackbar with the error message.
204 Snackbar.make(aboutVersionLayout, getString(R.string.error_saving_file, fileNameString, exception.toString()), Snackbar.LENGTH_INDEFINITE).show()
209 // Define the save about version image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
210 private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri ->
211 // Save the file if the URI is not null, which happens if the user exits the file picker by pressing back.
213 SaveAboutVersionImageCoroutine.saveImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout))
216 override fun onCreate(savedInstanceState: Bundle?) {
217 // Run the default commands.
218 super.onCreate(savedInstanceState)
220 // Store the arguments in class variables.
221 filterListsVersions = requireArguments().getStringArray(FILTERLISTS_VERSIONS)!!
223 // Enable the options menu for this fragment.
224 setHasOptionsMenu(true)
227 override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
228 // 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.
229 aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
231 // Get handles for the views.
232 privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
233 versionTextView = aboutVersionLayout.findViewById(R.id.version)
234 hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
235 brandTextView = aboutVersionLayout.findViewById(R.id.brand)
236 manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
237 modelTextView = aboutVersionLayout.findViewById(R.id.model)
238 deviceTextView = aboutVersionLayout.findViewById(R.id.device)
239 bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
240 radioTextView = aboutVersionLayout.findViewById(R.id.radio)
241 softwareTextView = aboutVersionLayout.findViewById(R.id.software)
242 androidTextView = aboutVersionLayout.findViewById(R.id.android)
243 securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
244 buildTextView = aboutVersionLayout.findViewById(R.id.build)
245 kernelTextView = aboutVersionLayout.findViewById(R.id.kernel)
246 webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
247 webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
248 orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
249 i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
250 openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
251 memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
252 appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
253 appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
254 appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
255 appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
256 systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
257 systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
258 systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
259 filterListsTextView = aboutVersionLayout.findViewById(R.id.filterlists)
260 easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
261 easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
262 fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
263 fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
264 ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
265 ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
266 packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
267 certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
268 certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
269 certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
270 certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
271 certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
272 certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
273 certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
276 val version = getString(R.string.version_code, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
277 val brandLabel = getString(R.string.brand)
278 val manufacturerLabel = getString(R.string.manufacturer)
279 val modelLabel = getString(R.string.model)
280 val deviceLabel = getString(R.string.device)
281 val bootloaderLabel = getString(R.string.bootloader)
282 val androidLabel = getString(R.string.android)
283 val securityPatchLabel = getString(R.string.security_patch)
284 val buildLabel = getString(R.string.build)
285 val kernelLabel = getString(R.string.kernel)
286 val webViewProviderLabel = getString(R.string.webview_provider)
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 // 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()>
310 val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
312 // Get the device's information and store it in strings.
313 val brand = Build.BRAND
314 val manufacturer = Build.MANUFACTURER
315 val model = Build.MODEL
316 val device = Build.DEVICE
317 val bootloader = Build.BOOTLOADER
318 val radio = Build.getRadioVersion()
319 val android = getString(R.string.api, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)
320 val securityPatch = Build.VERSION.SECURITY_PATCH
321 val build = Build.DISPLAY
322 val kernel = System.getProperty("os.version")
323 val webViewPackageName = webViewPackageInfo.packageName
324 val webViewVersion = webViewPackageInfo.versionName
326 // Get the Orbot version name if Orbot is installed.
327 val orbot: String = try {
328 // Store the version name.
329 requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
330 } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed.
331 // Store an empty string.
335 // Get the I2P version name if I2P is installed.
336 val i2p: String = try {
337 // Check to see if the F-Droid flavor is installed.
338 getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
339 } catch (exception: PackageManager.NameNotFoundException) { // The F-Droid flavor is not installed.
341 // Check to see if the F-Droid flavor is installed.
342 getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
343 } catch (exception: PackageManager.NameNotFoundException) { // The Google Play flavor is not installed either.
344 // Store an empty string.
349 // Get the OpenKeychain version name if it is installed.
350 val openKeychain: String = try {
351 // Store the version name.
352 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
353 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
354 // Store an empty string.
358 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
359 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
360 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
361 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
362 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
363 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
364 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
365 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
366 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
367 val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
368 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
369 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webViewVersion)
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 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
388 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389 kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
391 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
393 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
394 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
395 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
396 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
397 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
399 // Display the strings in the text boxes.
400 versionTextView.text = version
401 brandTextView.text = brandStringBuilder
402 manufacturerTextView.text = manufacturerStringBuilder
403 modelTextView.text = modelStringBuilder
404 deviceTextView.text = deviceStringBuilder
405 bootloaderTextView.text = bootloaderStringBuilder
406 androidTextView.text = androidStringBuilder
407 securityPatchTextView.text = securityPatchStringBuilder
408 buildTextView.text = buildStringBuilder
409 kernelTextView.text = kernelStringBuilder
410 webViewProviderTextView.text = webViewProviderStringBuilder
411 webViewVersionTextView.text = webViewVersionStringBuilder
412 easyListTextView.text = easyListStringBuilder
413 easyPrivacyTextView.text = easyPrivacyStringBuilder
414 fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
415 fanboySocialTextView.text = fanboySocialStringBuilder
416 ultraListTextView.text = ultraListStringBuilder
417 ultraPrivacyTextView.text = ultraPrivacyStringBuilder
419 // Only populate the radio text view if there is a radio in the device.
420 // 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>
421 if (radio != null && radio.isNotEmpty()) {
423 val radioLabel = getString(R.string.radio)
425 // Create a spannable string builder.
426 val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
428 // Set the span to display the radio in blue.
429 radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
431 // Display the string in the text view.
432 radioTextView.text = radioStringBuilder
433 } else { // This device does not have a radio.
434 // Hide the radio text view.
435 radioTextView.visibility = View.GONE
438 // Only populate the Orbot text view if it is installed.
439 if (orbot.isNotEmpty()) {
441 val orbotLabel = getString(R.string.orbot)
443 // Create a spannable string builder.
444 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
446 // Set the span to display the Orbot version.
447 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
449 // Display the string in the text view.
450 orbotTextView.text = orbotStringBuilder
451 } else { // Orbot is not installed.
452 // Hide the Orbot text view.
453 orbotTextView.visibility = View.GONE
456 // Only populate the I2P text view if it is installed.
457 if (i2p.isNotEmpty()) {
459 val i2pLabel = getString(R.string.i2p)
461 // Create a spannable string builder.
462 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
464 // Set the span to display the I2P version.
465 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
467 // Display the string in the text view.
468 i2pTextView.text = i2pStringBuilder
469 } else { // I2P is not installed.
470 // Hide the I2P text view.
471 i2pTextView.visibility = View.GONE
474 // Only populate the OpenKeychain text view if it is installed.
475 if (openKeychain.isNotEmpty()) {
477 val openKeychainLabel = getString(R.string.openkeychain)
479 // Create a spannable string builder.
480 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
482 // Set the span to display the OpenKeychain version.
483 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
485 // Display the string in the text view.
486 openKeychainTextView.text = openKeychainStringBuilder
487 } else { //OpenKeychain is not installed.
488 // Hide the OpenKeychain text view.
489 openKeychainTextView.visibility = View.GONE
492 // Display the package signature.
494 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
495 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
496 @Suppress("DEPRECATION")
497 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
500 // Convert the signature to a byte array input stream.
501 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
503 // Display the certificate information on the screen.
505 // Instantiate a certificate factory.
506 val certificateFactory = CertificateFactory.getInstance("X509")
508 // Generate an X509 certificate.
509 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
511 // Store the individual sections of the certificate.
512 val issuerDNPrincipal = x509Certificate.issuerDN
513 val subjectDNPrincipal = x509Certificate.subjectDN
514 val startDate = x509Certificate.notBefore
515 val endDate = x509Certificate.notAfter
516 val certificateVersion = x509Certificate.version
517 val serialNumberBigInteger = x509Certificate.serialNumber
518 val signatureAlgorithmNameString = x509Certificate.sigAlgName
520 // Create a spannable string builder for each text view that needs multiple colors of text.
521 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
522 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
523 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
524 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
525 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
526 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
527 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
529 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
530 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
531 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
532 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
533 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
534 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
535 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
536 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
538 // Display the strings in the text boxes.
539 certificateIssuerDnTextView.text = issuerDNStringBuilder
540 certificateSubjectDnTextView.text = subjectDNStringBuilder
541 certificateStartDateTextView.text = startDateStringBuilder
542 certificateEndDateTextView.text = endDataStringBuilder
543 certificateVersionTextView.text = certificateVersionStringBuilder
544 certificateSerialNumberTextView.text = serialNumberStringBuilder
545 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
546 } catch (certificateException: CertificateException) {
547 // Do nothing if there is a certificate error.
550 // Get a handle for the runtime.
551 runtime = Runtime.getRuntime()
553 // Get a handle for the activity manager.
554 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
556 // Instantiate a memory info variable.
557 memoryInfo = ActivityManager.MemoryInfo()
559 // Define a number format.
560 numberFormat = NumberFormat.getInstance()
562 // Set the minimum and maximum number of fraction digits.
563 numberFormat.minimumFractionDigits = 2
564 numberFormat.maximumFractionDigits = 2
566 // Update the memory usage.
567 updateMemoryUsage(requireActivity())
568 } catch (e: PackageManager.NameNotFoundException) {
569 // Do nothing if the package manager says Privacy Browser isn't installed.
572 // Scroll the tab if the saved instance state is not null.
573 if (savedInstanceState != null) {
574 aboutVersionLayout.post {
575 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
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 positions.
667 savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
668 savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
671 override fun onPause() {
672 // Run the default commands.
675 // Pause the updating of the memory usage.
676 updateMemoryUsageBoolean = false
679 override fun onResume() {
680 // Run the default commands.
683 // Resume the updating of the memory usage.
684 updateMemoryUsageBoolean = true
687 private fun updateMemoryUsage(activity: Activity) {
689 // Update the memory usage if enabled.
690 if (updateMemoryUsageBoolean) {
691 // Populate the memory info variable.
692 activityManager.getMemoryInfo(memoryInfo)
694 // Get the app memory information.
695 val appAvailableMemoryLong = runtime.freeMemory()
696 val appTotalMemoryLong = runtime.totalMemory()
697 val appMaximumMemoryLong = runtime.maxMemory()
699 // Calculate the app consumed memory.
700 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
702 // Get the system memory information.
703 val systemTotalMemoryLong = memoryInfo.totalMem
704 val systemAvailableMemoryLong = memoryInfo.availMem
706 // Calculate the system consumed memory.
707 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
709 // Convert the memory information into mebibytes.
710 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
711 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
712 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
713 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
714 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
715 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
716 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
718 // Get the mebibyte string.
719 val mebibyte = getString(R.string.mebibyte)
721 // Calculate the mebibyte length.
722 val mebibyteLength = mebibyte.length
724 // Create spannable string builders.
725 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
726 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
727 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
728 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
729 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
730 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
731 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
733 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
734 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
735 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
736 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
737 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
738 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
739 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
740 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
742 // Display the string in the text boxes.
743 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
744 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
745 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
746 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
747 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
748 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
749 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
752 // Schedule another memory update if the activity has not been destroyed.
753 if (!activity.isDestroyed) {
754 // Create a handler to update the memory usage.
755 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
757 // Create a runnable to update the memory usage.
758 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
760 // Update the memory usage after 1000 milliseconds
761 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
763 } catch (exception: Exception) {
768 private fun getAboutVersionString(): String {
769 // Initialize an about version string builder.
770 val aboutVersionStringBuilder = StringBuilder()
772 // Populate the about version string builder.
773 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
774 aboutVersionStringBuilder.append("\n")
775 aboutVersionStringBuilder.append(versionTextView.text)
776 aboutVersionStringBuilder.append("\n\n")
777 aboutVersionStringBuilder.append(hardwareTextView.text)
778 aboutVersionStringBuilder.append("\n")
779 aboutVersionStringBuilder.append(brandTextView.text)
780 aboutVersionStringBuilder.append("\n")
781 aboutVersionStringBuilder.append(manufacturerTextView.text)
782 aboutVersionStringBuilder.append("\n")
783 aboutVersionStringBuilder.append(modelTextView.text)
784 aboutVersionStringBuilder.append("\n")
785 aboutVersionStringBuilder.append(deviceTextView.text)
786 aboutVersionStringBuilder.append("\n")
787 aboutVersionStringBuilder.append(bootloaderTextView.text)
788 aboutVersionStringBuilder.append("\n")
789 if (radioTextView.visibility == View.VISIBLE) {
790 aboutVersionStringBuilder.append(radioTextView.text)
791 aboutVersionStringBuilder.append("\n")
793 aboutVersionStringBuilder.append("\n")
794 aboutVersionStringBuilder.append(softwareTextView.text)
795 aboutVersionStringBuilder.append("\n")
796 aboutVersionStringBuilder.append(androidTextView.text)
797 aboutVersionStringBuilder.append("\n")
798 if (securityPatchTextView.visibility == View.VISIBLE) {
799 aboutVersionStringBuilder.append(securityPatchTextView.text)
800 aboutVersionStringBuilder.append("\n")
802 aboutVersionStringBuilder.append(buildTextView.text)
803 aboutVersionStringBuilder.append("\n")
804 aboutVersionStringBuilder.append(kernelTextView.text)
805 aboutVersionStringBuilder.append("\n")
806 if (webViewProviderTextView.visibility == View.VISIBLE) {
807 aboutVersionStringBuilder.append(webViewProviderTextView.text)
808 aboutVersionStringBuilder.append("\n")
810 aboutVersionStringBuilder.append(webViewVersionTextView.text)
811 aboutVersionStringBuilder.append("\n")
812 if (orbotTextView.visibility == View.VISIBLE) {
813 aboutVersionStringBuilder.append(orbotTextView.text)
814 aboutVersionStringBuilder.append("\n")
816 if (i2pTextView.visibility == View.VISIBLE) {
817 aboutVersionStringBuilder.append(i2pTextView.text)
818 aboutVersionStringBuilder.append("\n")
820 if (openKeychainTextView.visibility == View.VISIBLE) {
821 aboutVersionStringBuilder.append(openKeychainTextView.text)
822 aboutVersionStringBuilder.append("\n")
824 aboutVersionStringBuilder.append("\n")
825 aboutVersionStringBuilder.append(memoryUsageTextView.text)
826 aboutVersionStringBuilder.append("\n")
827 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
828 aboutVersionStringBuilder.append("\n")
829 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
830 aboutVersionStringBuilder.append("\n")
831 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
832 aboutVersionStringBuilder.append("\n")
833 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
834 aboutVersionStringBuilder.append("\n")
835 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
836 aboutVersionStringBuilder.append("\n")
837 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
838 aboutVersionStringBuilder.append("\n")
839 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
840 aboutVersionStringBuilder.append("\n\n")
841 aboutVersionStringBuilder.append(filterListsTextView.text)
842 aboutVersionStringBuilder.append("\n")
843 aboutVersionStringBuilder.append(easyListTextView.text)
844 aboutVersionStringBuilder.append("\n")
845 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
846 aboutVersionStringBuilder.append("\n")
847 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
848 aboutVersionStringBuilder.append("\n")
849 aboutVersionStringBuilder.append(fanboySocialTextView.text)
850 aboutVersionStringBuilder.append("\n")
851 aboutVersionStringBuilder.append(ultraListTextView.text)
852 aboutVersionStringBuilder.append("\n")
853 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
854 aboutVersionStringBuilder.append("\n\n")
855 aboutVersionStringBuilder.append(packageSignatureTextView.text)
856 aboutVersionStringBuilder.append("\n")
857 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
858 aboutVersionStringBuilder.append("\n")
859 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
860 aboutVersionStringBuilder.append("\n")
861 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
862 aboutVersionStringBuilder.append("\n")
863 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
864 aboutVersionStringBuilder.append("\n")
865 aboutVersionStringBuilder.append(certificateVersionTextView.text)
866 aboutVersionStringBuilder.append("\n")
867 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
868 aboutVersionStringBuilder.append("\n")
869 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
871 // Return the string.
872 return aboutVersionStringBuilder.toString()