2 * Copyright 2016-2024 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
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 // Get a cursor from the content resolver.
166 val contentResolverCursor = requireActivity().contentResolver.query(fileUri, null, null, null)!!
168 // Move to the first row.
169 contentResolverCursor.moveToFirst()
171 // Get the file name from the cursor.
172 val fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
175 contentResolverCursor.close()
178 // Get the about version string.
179 val aboutVersionString = getAboutVersionString()
181 // Open an output stream.
182 val outputStream = requireActivity().contentResolver.openOutputStream(fileUri)!!
184 // Save about version using a coroutine with Dispatchers.IO.
185 CoroutineScope(Dispatchers.Main).launch {
186 withContext(Dispatchers.IO) {
187 // Write the about version string to the output stream.
188 outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
190 // Close the output stream.
195 // Display a snackbar with the saved logcat information.
196 Snackbar.make(aboutVersionLayout, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
197 } catch (exception: Exception) {
198 // Display a snackbar with the error message.
199 Snackbar.make(aboutVersionLayout, getString(R.string.error_saving_file, fileNameString, exception.toString()), Snackbar.LENGTH_INDEFINITE).show()
204 // Define the save about version image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
205 private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri ->
206 // Save the file if the URI is not null, which happens if the user exits the file picker by pressing back.
208 SaveAboutVersionImageCoroutine.saveImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout))
211 override fun onCreate(savedInstanceState: Bundle?) {
212 // Run the default commands.
213 super.onCreate(savedInstanceState)
215 // Store the arguments in class variables.
216 filterListsVersions = requireArguments().getStringArray(FILTERLISTS_VERSIONS)!!
218 // Enable the options menu for this fragment.
219 setHasOptionsMenu(true)
222 override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
223 // 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.
224 aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
226 // Get handles for the views.
227 privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
228 versionTextView = aboutVersionLayout.findViewById(R.id.version)
229 hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
230 brandTextView = aboutVersionLayout.findViewById(R.id.brand)
231 manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
232 modelTextView = aboutVersionLayout.findViewById(R.id.model)
233 deviceTextView = aboutVersionLayout.findViewById(R.id.device)
234 bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
235 radioTextView = aboutVersionLayout.findViewById(R.id.radio)
236 softwareTextView = aboutVersionLayout.findViewById(R.id.software)
237 androidTextView = aboutVersionLayout.findViewById(R.id.android)
238 securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
239 buildTextView = aboutVersionLayout.findViewById(R.id.build)
240 kernelTextView = aboutVersionLayout.findViewById(R.id.kernel)
241 webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
242 webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
243 orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
244 i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
245 openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
246 memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
247 appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
248 appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
249 appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
250 appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
251 systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
252 systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
253 systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
254 filterListsTextView = aboutVersionLayout.findViewById(R.id.filterlists)
255 easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
256 easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
257 fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
258 fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
259 ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
260 ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
261 packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
262 certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
263 certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
264 certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
265 certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
266 certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
267 certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
268 certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
271 val version = getString(R.string.version_code, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
272 val brandLabel = getString(R.string.brand)
273 val manufacturerLabel = getString(R.string.manufacturer)
274 val modelLabel = getString(R.string.model)
275 val deviceLabel = getString(R.string.device)
276 val bootloaderLabel = getString(R.string.bootloader)
277 val androidLabel = getString(R.string.android)
278 val securityPatchLabel = getString(R.string.security_patch)
279 val buildLabel = getString(R.string.build)
280 val kernelLabel = getString(R.string.kernel)
281 val webViewProviderLabel = getString(R.string.webview_provider)
282 val webViewVersionLabel = getString(R.string.webview_version)
283 appConsumedMemoryLabel = getString(R.string.app_consumed_memory)
284 appAvailableMemoryLabel = getString(R.string.app_available_memory)
285 appTotalMemoryLabel = getString(R.string.app_total_memory)
286 appMaximumMemoryLabel = getString(R.string.app_maximum_memory)
287 systemConsumedMemoryLabel = getString(R.string.system_consumed_memory)
288 systemAvailableMemoryLabel = getString(R.string.system_available_memory)
289 systemTotalMemoryLabel = getString(R.string.system_total_memory)
290 val easyListLabel = getString(R.string.easylist_label)
291 val easyPrivacyLabel = getString(R.string.easyprivacy_label)
292 val fanboyAnnoyanceLabel = getString(R.string.fanboys_annoyance_label)
293 val fanboySocialLabel = getString(R.string.fanboys_social_label)
294 val ultraListLabel = getString(R.string.ultralist_label)
295 val ultraPrivacyLabel = getString(R.string.ultraprivacy_label)
296 val issuerDNLabel = getString(R.string.issuer_dn)
297 val subjectDNLabel = getString(R.string.subject_dn)
298 val startDateLabel = getString(R.string.start_date)
299 val endDateLabel = getString(R.string.end_date)
300 val certificateVersionLabel = getString(R.string.certificate_version)
301 val serialNumberLabel = getString(R.string.serial_number)
302 val signatureAlgorithmLabel = getString(R.string.signature_algorithm)
304 // Get the current WebView package info.
305 val webViewPackageInfo = WebView.getCurrentWebViewPackage()!!
307 // Get the device's information and store it in strings.
308 val brand = Build.BRAND
309 val manufacturer = Build.MANUFACTURER
310 val model = Build.MODEL
311 val device = Build.DEVICE
312 val bootloader = Build.BOOTLOADER
313 val radio = Build.getRadioVersion()
314 val android = getString(R.string.api, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)
315 val securityPatch = Build.VERSION.SECURITY_PATCH
316 val build = Build.DISPLAY
317 val kernel = System.getProperty("os.version")
318 val webViewPackageName = webViewPackageInfo.packageName
319 val webViewVersion = webViewPackageInfo.versionName
321 // Get the Orbot version name if Orbot is installed.
322 val orbot: String = try {
323 // Store the version name.
324 requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
325 } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed.
326 // Store an empty string.
330 // Get the I2P version name if I2P is installed.
331 val i2p: String = try {
332 // Check to see if the F-Droid flavor is installed.
333 getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
334 } catch (exception: PackageManager.NameNotFoundException) { // The F-Droid flavor is not installed.
336 // Check to see if the F-Droid flavor is installed.
337 getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
338 } catch (exception: PackageManager.NameNotFoundException) { // The Google Play flavor is not installed either.
339 // Store an empty string.
344 // Get the OpenKeychain version name if it is installed.
345 val openKeychain: String = try {
346 // Store the version name.
347 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
348 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
349 // Store an empty string.
353 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
354 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
355 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
356 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
357 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
358 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
359 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
360 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
361 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
362 val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
363 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
364 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webViewVersion)
365 val easyListStringBuilder = SpannableStringBuilder(easyListLabel + filterListsVersions[0])
366 val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + filterListsVersions[1])
367 val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + filterListsVersions[2])
368 val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + filterListsVersions[3])
369 val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + filterListsVersions[4])
370 val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + filterListsVersions[5])
372 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
373 blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
375 // Set the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
376 brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
377 manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
378 modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
379 deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
380 bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
381 androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
382 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
383 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
384 kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
385 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
386 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
387 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
388 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
391 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
394 // Display the strings in the text boxes.
395 versionTextView.text = version
396 brandTextView.text = brandStringBuilder
397 manufacturerTextView.text = manufacturerStringBuilder
398 modelTextView.text = modelStringBuilder
399 deviceTextView.text = deviceStringBuilder
400 bootloaderTextView.text = bootloaderStringBuilder
401 androidTextView.text = androidStringBuilder
402 securityPatchTextView.text = securityPatchStringBuilder
403 buildTextView.text = buildStringBuilder
404 kernelTextView.text = kernelStringBuilder
405 webViewProviderTextView.text = webViewProviderStringBuilder
406 webViewVersionTextView.text = webViewVersionStringBuilder
407 easyListTextView.text = easyListStringBuilder
408 easyPrivacyTextView.text = easyPrivacyStringBuilder
409 fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
410 fanboySocialTextView.text = fanboySocialStringBuilder
411 ultraListTextView.text = ultraListStringBuilder
412 ultraPrivacyTextView.text = ultraPrivacyStringBuilder
414 // Only populate the radio text view if there is a radio in the device.
415 // 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>
416 if (radio != null && radio.isNotEmpty()) {
418 val radioLabel = getString(R.string.radio)
420 // Create a spannable string builder.
421 val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
423 // Set the span to display the radio in blue.
424 radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
426 // Display the string in the text view.
427 radioTextView.text = radioStringBuilder
428 } else { // This device does not have a radio.
429 // Hide the radio text view.
430 radioTextView.visibility = View.GONE
433 // Only populate the Orbot text view if it is installed.
434 if (orbot.isNotEmpty()) {
436 val orbotLabel = getString(R.string.orbot)
438 // Create a spannable string builder.
439 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
441 // Set the span to display the Orbot version.
442 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
444 // Display the string in the text view.
445 orbotTextView.text = orbotStringBuilder
446 } else { // Orbot is not installed.
447 // Hide the Orbot text view.
448 orbotTextView.visibility = View.GONE
451 // Only populate the I2P text view if it is installed.
452 if (i2p.isNotEmpty()) {
454 val i2pLabel = getString(R.string.i2p)
456 // Create a spannable string builder.
457 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
459 // Set the span to display the I2P version.
460 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
462 // Display the string in the text view.
463 i2pTextView.text = i2pStringBuilder
464 } else { // I2P is not installed.
465 // Hide the I2P text view.
466 i2pTextView.visibility = View.GONE
469 // Only populate the OpenKeychain text view if it is installed.
470 if (openKeychain.isNotEmpty()) {
472 val openKeychainLabel = getString(R.string.openkeychain)
474 // Create a spannable string builder.
475 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
477 // Set the span to display the OpenKeychain version.
478 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
480 // Display the string in the text view.
481 openKeychainTextView.text = openKeychainStringBuilder
482 } else { //OpenKeychain is not installed.
483 // Hide the OpenKeychain text view.
484 openKeychainTextView.visibility = View.GONE
487 // Display the package signature.
489 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
490 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
491 @Suppress("DEPRECATION")
492 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
495 // Convert the signature to a byte array input stream.
496 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
498 // Display the certificate information on the screen.
500 // Instantiate a certificate factory.
501 val certificateFactory = CertificateFactory.getInstance("X509")
503 // Generate an X509 certificate.
504 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
506 // Store the individual sections of the certificate.
507 val issuerDNPrincipal = x509Certificate.issuerDN
508 val subjectDNPrincipal = x509Certificate.subjectDN
509 val startDate = x509Certificate.notBefore
510 val endDate = x509Certificate.notAfter
511 val certificateVersion = x509Certificate.version
512 val serialNumberBigInteger = x509Certificate.serialNumber
513 val signatureAlgorithmNameString = x509Certificate.sigAlgName
515 // Create a spannable string builder for each text view that needs multiple colors of text.
516 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
517 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
518 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
519 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
520 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
521 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
522 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
524 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
525 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
526 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
527 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
528 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
529 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
530 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
531 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
533 // Display the strings in the text boxes.
534 certificateIssuerDnTextView.text = issuerDNStringBuilder
535 certificateSubjectDnTextView.text = subjectDNStringBuilder
536 certificateStartDateTextView.text = startDateStringBuilder
537 certificateEndDateTextView.text = endDataStringBuilder
538 certificateVersionTextView.text = certificateVersionStringBuilder
539 certificateSerialNumberTextView.text = serialNumberStringBuilder
540 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
541 } catch (certificateException: CertificateException) {
542 // Do nothing if there is a certificate error.
545 // Get a handle for the runtime.
546 runtime = Runtime.getRuntime()
548 // Get a handle for the activity manager.
549 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
551 // Instantiate a memory info variable.
552 memoryInfo = ActivityManager.MemoryInfo()
554 // Define a number format.
555 numberFormat = NumberFormat.getInstance()
557 // Set the minimum and maximum number of fraction digits.
558 numberFormat.minimumFractionDigits = 2
559 numberFormat.maximumFractionDigits = 2
561 // Update the memory usage.
562 updateMemoryUsage(requireActivity())
563 } catch (e: PackageManager.NameNotFoundException) {
564 // Do nothing if the package manager says Privacy Browser isn't installed.
567 // Scroll the tab if the saved instance state is not null.
568 if (savedInstanceState != null) {
569 aboutVersionLayout.post {
570 aboutVersionLayout.scrollY = savedInstanceState.getInt(SCROLL_Y)
574 // Return the tab layout.
575 return aboutVersionLayout
578 override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
579 // Inflate the about version menu.
580 menuInflater.inflate(R.menu.about_version_options_menu, menu)
582 // Run the default commands.
583 super.onCreateOptionsMenu(menu, menuInflater)
586 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
587 // Run the appropriate commands.
588 when (menuItem.itemId) {
589 R.id.copy -> { // Copy.
590 // Get the about version string.
591 val aboutVersionString = getAboutVersionString()
593 // Get a handle for the clipboard manager.
594 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
596 // Place the about version string in a clip data.
597 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
599 // Place the clip data on the clipboard.
600 clipboardManager.setPrimaryClip(aboutVersionClipData)
602 // Display a snackbar if the API <= 32 (Android 12L). Beginning in Android 13 the OS displays a notification that covers up the snackbar.
603 if (Build.VERSION.SDK_INT <= 32)
604 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
606 // Consume the event.
610 R.id.share -> { // Share.
611 // Get the about version string.
612 val aboutString = getAboutVersionString()
614 // Create a share intent.
615 val shareIntent = Intent(Intent.ACTION_SEND)
617 // Add the about version string to the intent.
618 shareIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
620 // Set the MIME type.
621 shareIntent.type = "text/plain"
623 // Set the intent to open in a new task.
624 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
627 startActivity(Intent.createChooser(shareIntent, getString(R.string.share)))
629 // Consume the event.
633 R.id.save_text -> { // Save text.
634 // Open the file picker.
635 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
637 // Consume the event.
641 R.id.save_image -> { // Save image.
642 // Open the file picker.
643 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
645 // Consume the event.
649 else -> { // The home button was selected.
650 // Run the parents class on return.
651 return super.onOptionsItemSelected(menuItem)
656 override fun onSaveInstanceState(savedInstanceState: Bundle) {
657 // Run the default commands.
658 super.onSaveInstanceState(savedInstanceState)
660 // Save the scroll position.
661 savedInstanceState.putInt(SCROLL_Y, aboutVersionLayout.scrollY)
664 override fun onPause() {
665 // Run the default commands.
668 // Pause the updating of the memory usage.
669 updateMemoryUsageBoolean = false
672 override fun onResume() {
673 // Run the default commands.
676 // Resume the updating of the memory usage.
677 updateMemoryUsageBoolean = true
680 private fun updateMemoryUsage(activity: Activity) {
682 // Update the memory usage if enabled.
683 if (updateMemoryUsageBoolean) {
684 // Populate the memory info variable.
685 activityManager.getMemoryInfo(memoryInfo)
687 // Get the app memory information.
688 val appAvailableMemoryLong = runtime.freeMemory()
689 val appTotalMemoryLong = runtime.totalMemory()
690 val appMaximumMemoryLong = runtime.maxMemory()
692 // Calculate the app consumed memory.
693 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
695 // Get the system memory information.
696 val systemTotalMemoryLong = memoryInfo.totalMem
697 val systemAvailableMemoryLong = memoryInfo.availMem
699 // Calculate the system consumed memory.
700 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
702 // Convert the memory information into mebibytes.
703 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
704 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
705 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
706 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
707 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
708 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
709 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
711 // Get the mebibyte string.
712 val mebibyte = getString(R.string.mebibyte)
714 // Calculate the mebibyte length.
715 val mebibyteLength = mebibyte.length
717 // Create spannable string builders.
718 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
719 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
720 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
721 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
722 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
723 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
724 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
726 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
727 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
728 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
729 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
730 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
731 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
732 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
733 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
735 // Display the string in the text boxes.
736 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
737 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
738 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
739 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
740 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
741 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
742 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
745 // Schedule another memory update if the activity has not been destroyed.
746 if (!activity.isDestroyed) {
747 // Create a handler to update the memory usage.
748 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
750 // Create a runnable to update the memory usage.
751 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
753 // Update the memory usage after 1000 milliseconds
754 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
756 } catch (exception: Exception) {
761 private fun getAboutVersionString(): String {
762 // Initialize an about version string builder.
763 val aboutVersionStringBuilder = StringBuilder()
765 // Populate the about version string builder.
766 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
767 aboutVersionStringBuilder.append("\n")
768 aboutVersionStringBuilder.append(versionTextView.text)
769 aboutVersionStringBuilder.append("\n\n")
770 aboutVersionStringBuilder.append(hardwareTextView.text)
771 aboutVersionStringBuilder.append("\n")
772 aboutVersionStringBuilder.append(brandTextView.text)
773 aboutVersionStringBuilder.append("\n")
774 aboutVersionStringBuilder.append(manufacturerTextView.text)
775 aboutVersionStringBuilder.append("\n")
776 aboutVersionStringBuilder.append(modelTextView.text)
777 aboutVersionStringBuilder.append("\n")
778 aboutVersionStringBuilder.append(deviceTextView.text)
779 aboutVersionStringBuilder.append("\n")
780 aboutVersionStringBuilder.append(bootloaderTextView.text)
781 aboutVersionStringBuilder.append("\n")
782 if (radioTextView.visibility == View.VISIBLE) {
783 aboutVersionStringBuilder.append(radioTextView.text)
784 aboutVersionStringBuilder.append("\n")
786 aboutVersionStringBuilder.append("\n")
787 aboutVersionStringBuilder.append(softwareTextView.text)
788 aboutVersionStringBuilder.append("\n")
789 aboutVersionStringBuilder.append(androidTextView.text)
790 aboutVersionStringBuilder.append("\n")
791 if (securityPatchTextView.visibility == View.VISIBLE) {
792 aboutVersionStringBuilder.append(securityPatchTextView.text)
793 aboutVersionStringBuilder.append("\n")
795 aboutVersionStringBuilder.append(buildTextView.text)
796 aboutVersionStringBuilder.append("\n")
797 aboutVersionStringBuilder.append(kernelTextView.text)
798 aboutVersionStringBuilder.append("\n")
799 if (webViewProviderTextView.visibility == View.VISIBLE) {
800 aboutVersionStringBuilder.append(webViewProviderTextView.text)
801 aboutVersionStringBuilder.append("\n")
803 aboutVersionStringBuilder.append(webViewVersionTextView.text)
804 aboutVersionStringBuilder.append("\n")
805 if (orbotTextView.visibility == View.VISIBLE) {
806 aboutVersionStringBuilder.append(orbotTextView.text)
807 aboutVersionStringBuilder.append("\n")
809 if (i2pTextView.visibility == View.VISIBLE) {
810 aboutVersionStringBuilder.append(i2pTextView.text)
811 aboutVersionStringBuilder.append("\n")
813 if (openKeychainTextView.visibility == View.VISIBLE) {
814 aboutVersionStringBuilder.append(openKeychainTextView.text)
815 aboutVersionStringBuilder.append("\n")
817 aboutVersionStringBuilder.append("\n")
818 aboutVersionStringBuilder.append(memoryUsageTextView.text)
819 aboutVersionStringBuilder.append("\n")
820 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
821 aboutVersionStringBuilder.append("\n")
822 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
823 aboutVersionStringBuilder.append("\n")
824 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
825 aboutVersionStringBuilder.append("\n")
826 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
827 aboutVersionStringBuilder.append("\n")
828 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
829 aboutVersionStringBuilder.append("\n")
830 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
831 aboutVersionStringBuilder.append("\n")
832 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
833 aboutVersionStringBuilder.append("\n\n")
834 aboutVersionStringBuilder.append(filterListsTextView.text)
835 aboutVersionStringBuilder.append("\n")
836 aboutVersionStringBuilder.append(easyListTextView.text)
837 aboutVersionStringBuilder.append("\n")
838 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
839 aboutVersionStringBuilder.append("\n")
840 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
841 aboutVersionStringBuilder.append("\n")
842 aboutVersionStringBuilder.append(fanboySocialTextView.text)
843 aboutVersionStringBuilder.append("\n")
844 aboutVersionStringBuilder.append(ultraListTextView.text)
845 aboutVersionStringBuilder.append("\n")
846 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
847 aboutVersionStringBuilder.append("\n\n")
848 aboutVersionStringBuilder.append(packageSignatureTextView.text)
849 aboutVersionStringBuilder.append("\n")
850 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
851 aboutVersionStringBuilder.append("\n")
852 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
853 aboutVersionStringBuilder.append("\n")
854 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
855 aboutVersionStringBuilder.append("\n")
856 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
857 aboutVersionStringBuilder.append("\n")
858 aboutVersionStringBuilder.append(certificateVersionTextView.text)
859 aboutVersionStringBuilder.append("\n")
860 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
861 aboutVersionStringBuilder.append("\n")
862 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
864 // Return the string.
865 return aboutVersionStringBuilder.toString()