2 * Copyright 2016-2023 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
6 * Privacy Browser Android is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser Android is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser Android. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.fragments
22 import android.annotation.SuppressLint
23 import android.app.Activity
24 import android.app.ActivityManager
25 import android.content.ClipData
26 import android.content.ClipboardManager
27 import android.content.Context
28 import android.content.Intent
29 import android.content.pm.PackageManager
30 import android.os.Build
31 import android.os.Bundle
32 import android.os.Handler
33 import android.os.Looper
34 import android.provider.OpenableColumns
35 import android.text.SpannableStringBuilder
36 import android.text.Spanned
37 import android.text.style.ForegroundColorSpan
38 import android.view.LayoutInflater
39 import android.view.Menu
40 import android.view.MenuInflater
41 import android.view.MenuItem
42 import android.view.View
43 import android.view.ViewGroup
44 import android.webkit.WebView
45 import android.widget.TextView
47 import androidx.activity.result.contract.ActivityResultContracts
48 import androidx.fragment.app.Fragment
49 import androidx.webkit.WebViewCompat
51 import com.google.android.material.snackbar.Snackbar
53 import com.stoutner.privacybrowser.BuildConfig
54 import com.stoutner.privacybrowser.R
55 import com.stoutner.privacybrowser.coroutines.SaveAboutVersionImageCoroutine
57 import kotlinx.coroutines.CoroutineScope
58 import kotlinx.coroutines.Dispatchers
59 import kotlinx.coroutines.launch
60 import kotlinx.coroutines.withContext
62 import java.io.ByteArrayInputStream
63 import java.io.InputStream
64 import java.lang.Exception
65 import java.nio.charset.StandardCharsets
66 import java.security.cert.CertificateException
67 import java.security.cert.CertificateFactory
68 import java.security.cert.X509Certificate
69 import java.text.DateFormat
70 import java.text.NumberFormat
72 import kotlin.text.StringBuilder
74 // Define the class constants.
75 private const val FILTERLISTS_VERSIONS = "filterlists_versions"
76 private const val MEBIBYTE = 1048576
78 class AboutVersionFragment : Fragment() {
79 // Define the class variables.
80 private var updateMemoryUsageBoolean = true
82 // Declare the class variables.
83 private lateinit var aboutVersionLayout: View
84 private lateinit var activityManager: ActivityManager
85 private lateinit var appAvailableMemoryLabel: String
86 private lateinit var appConsumedMemoryLabel: String
87 private lateinit var appMaximumMemoryLabel: String
88 private lateinit var appTotalMemoryLabel: String
89 private lateinit var blueColorSpan: ForegroundColorSpan
90 private lateinit var filterListsVersions: Array<String>
91 private lateinit var memoryInfo: ActivityManager.MemoryInfo
92 private lateinit var numberFormat: NumberFormat
93 private lateinit var runtime: Runtime
94 private lateinit var systemAvailableMemoryLabel: String
95 private lateinit var systemConsumedMemoryLabel: String
96 private lateinit var systemTotalMemoryLabel: String
98 // Declare the class views.
99 private lateinit var androidTextView: TextView
100 private lateinit var appAvailableMemoryTextView: TextView
101 private lateinit var appConsumedMemoryTextView: TextView
102 private lateinit var appMaximumMemoryTextView: TextView
103 private lateinit var appTotalMemoryTextView: TextView
104 private lateinit var brandTextView: TextView
105 private lateinit var bootloaderTextView: TextView
106 private lateinit var certificateEndDateTextView: TextView
107 private lateinit var certificateIssuerDnTextView: TextView
108 private lateinit var certificateSerialNumberTextView: TextView
109 private lateinit var certificateSignatureAlgorithmTextView: TextView
110 private lateinit var certificateStartDateTextView: TextView
111 private lateinit var certificateSubjectDnTextView: TextView
112 private lateinit var certificateVersionTextView: TextView
113 private lateinit var buildTextView: TextView
114 private lateinit var deviceTextView: TextView
115 private lateinit var easyListTextView: TextView
116 private lateinit var easyPrivacyTextView: TextView
117 private lateinit var fanboyAnnoyanceTextView: TextView
118 private lateinit var fanboySocialTextView: TextView
119 private lateinit var filterListsTextView: TextView
120 private lateinit var hardwareTextView: TextView
121 private lateinit var i2pTextView: TextView
122 private lateinit var kernelTextView: TextView
123 private lateinit var manufacturerTextView: TextView
124 private lateinit var memoryUsageTextView: TextView
125 private lateinit var modelTextView: TextView
126 private lateinit var openKeychainTextView: TextView
127 private lateinit var orbotTextView: TextView
128 private lateinit var packageSignatureTextView: TextView
129 private lateinit var privacyBrowserTextView: TextView
130 private lateinit var radioTextView: TextView
131 private lateinit var securityPatchTextView: TextView
132 private lateinit var softwareTextView: TextView
133 private lateinit var systemAvailableMemoryTextView: TextView
134 private lateinit var systemConsumedMemoryTextView: TextView
135 private lateinit var systemTotalMemoryTextView: TextView
136 private lateinit var versionTextView: TextView
137 private lateinit var ultraListTextView: TextView
138 private lateinit var ultraPrivacyTextView: TextView
139 private lateinit var webViewProviderTextView: TextView
140 private lateinit var webViewVersionTextView: TextView
143 fun createTab(filterListsVersions: Array<String>): AboutVersionFragment {
144 // Create an arguments bundle.
145 val argumentsBundle = Bundle()
147 // Store the arguments in the bundle.
148 argumentsBundle.putStringArray(FILTERLISTS_VERSIONS, filterListsVersions)
150 // Create a new instance of the tab fragment.
151 val aboutVersionFragment = AboutVersionFragment()
153 // Add the arguments bundle to the fragment.
154 aboutVersionFragment.arguments = argumentsBundle
156 // Return the new fragment.
157 return aboutVersionFragment
161 // Define the save about version text activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
162 private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri ->
163 // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
164 if (fileUri != null) {
165 // Initialize the file name string from the file URI last path segment.
166 var fileNameString = fileUri.lastPathSegment
168 // Query the exact file name if the API >= 26.
169 if (Build.VERSION.SDK_INT >= 26) {
170 // Get a cursor from the content resolver.
171 val contentResolverCursor = requireActivity().contentResolver.query(fileUri, null, null, null)!!
173 // Move to the first row.
174 contentResolverCursor.moveToFirst()
176 // Get the file name from the cursor.
177 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
180 contentResolverCursor.close()
184 // Get the about version string.
185 val aboutVersionString = getAboutVersionString()
187 // Open an output stream.
188 val outputStream = requireActivity().contentResolver.openOutputStream(fileUri)!!
190 // Save about version using a coroutine with Dispatchers.IO.
191 CoroutineScope(Dispatchers.Main).launch {
192 withContext(Dispatchers.IO) {
193 // Write the about version string to the output stream.
194 outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
196 // Close the output stream.
201 // Display a snackbar with the saved logcat information.
202 Snackbar.make(aboutVersionLayout, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
203 } catch (exception: Exception) {
204 // Display a snackbar with the error message.
205 Snackbar.make(aboutVersionLayout, getString(R.string.error_saving_file, fileNameString, exception.toString()), Snackbar.LENGTH_INDEFINITE).show()
210 // Define the save about version image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
211 private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri ->
212 // Save the file if the URI is not null, which happens if the user exits the file picker by pressing back.
214 SaveAboutVersionImageCoroutine.saveImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout))
217 override fun onCreate(savedInstanceState: Bundle?) {
218 // Run the default commands.
219 super.onCreate(savedInstanceState)
221 // Store the arguments in class variables.
222 filterListsVersions = requireArguments().getStringArray(FILTERLISTS_VERSIONS)!!
224 // Enable the options menu for this fragment.
225 setHasOptionsMenu(true)
228 override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
229 // Inflate the layout. Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container. The fragment will take care of attaching the root automatically.
230 aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
232 // Get handles for the views.
233 privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
234 versionTextView = aboutVersionLayout.findViewById(R.id.version)
235 hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
236 brandTextView = aboutVersionLayout.findViewById(R.id.brand)
237 manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
238 modelTextView = aboutVersionLayout.findViewById(R.id.model)
239 deviceTextView = aboutVersionLayout.findViewById(R.id.device)
240 bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
241 radioTextView = aboutVersionLayout.findViewById(R.id.radio)
242 softwareTextView = aboutVersionLayout.findViewById(R.id.software)
243 androidTextView = aboutVersionLayout.findViewById(R.id.android)
244 securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
245 buildTextView = aboutVersionLayout.findViewById(R.id.build)
246 kernelTextView = aboutVersionLayout.findViewById(R.id.kernel)
247 webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
248 webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
249 orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
250 i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
251 openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
252 memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
253 appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
254 appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
255 appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
256 appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
257 systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
258 systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
259 systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
260 filterListsTextView = aboutVersionLayout.findViewById(R.id.filterlists)
261 easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
262 easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
263 fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
264 fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
265 ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
266 ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
267 packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
268 certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
269 certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
270 certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
271 certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
272 certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
273 certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
274 certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
277 val version = getString(R.string.version_code, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
278 val brandLabel = getString(R.string.brand)
279 val manufacturerLabel = getString(R.string.manufacturer)
280 val modelLabel = getString(R.string.model)
281 val deviceLabel = getString(R.string.device)
282 val bootloaderLabel = getString(R.string.bootloader)
283 val androidLabel = getString(R.string.android)
284 val buildLabel = getString(R.string.build)
285 val kernelLabel = getString(R.string.kernel)
286 val webViewVersionLabel = getString(R.string.webview_version)
287 appConsumedMemoryLabel = getString(R.string.app_consumed_memory)
288 appAvailableMemoryLabel = getString(R.string.app_available_memory)
289 appTotalMemoryLabel = getString(R.string.app_total_memory)
290 appMaximumMemoryLabel = getString(R.string.app_maximum_memory)
291 systemConsumedMemoryLabel = getString(R.string.system_consumed_memory)
292 systemAvailableMemoryLabel = getString(R.string.system_available_memory)
293 systemTotalMemoryLabel = getString(R.string.system_total_memory)
294 val easyListLabel = getString(R.string.easylist_label)
295 val easyPrivacyLabel = getString(R.string.easyprivacy_label)
296 val fanboyAnnoyanceLabel = getString(R.string.fanboys_annoyance_label)
297 val fanboySocialLabel = getString(R.string.fanboys_social_label)
298 val ultraListLabel = getString(R.string.ultralist_label)
299 val ultraPrivacyLabel = getString(R.string.ultraprivacy_label)
300 val issuerDNLabel = getString(R.string.issuer_dn)
301 val subjectDNLabel = getString(R.string.subject_dn)
302 val startDateLabel = getString(R.string.start_date)
303 val endDateLabel = getString(R.string.end_date)
304 val certificateVersionLabel = getString(R.string.certificate_version)
305 val serialNumberLabel = getString(R.string.serial_number)
306 val signatureAlgorithmLabel = getString(R.string.signature_algorithm)
308 // The WebView layout is only used to get the default user agent from `bare_webview`. It is not used to render content on the screen.
309 // Once the minimum API >= 26 this can be accomplished with the WebView package info.
310 val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false)
311 val tabLayoutWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
312 val userAgentString = tabLayoutWebView.settings.userAgentString
314 // Get the device's information and store it in strings.
315 val brand = Build.BRAND
316 val manufacturer = Build.MANUFACTURER
317 val model = Build.MODEL
318 val device = Build.DEVICE
319 val bootloader = Build.BOOTLOADER
320 val radio = Build.getRadioVersion()
321 val android = getString(R.string.api, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)
322 val build = Build.DISPLAY
323 val kernel = System.getProperty("os.version")
325 // Get the WebView version, selecting the substring that begins after `Chrome/` and goes until the next ` `.
326 val webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")))
328 // Get the Orbot version name if Orbot is installed.
329 val orbot: String = try {
330 // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33.
331 @Suppress("DEPRECATION")
332 requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
333 } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed.
334 // Store an empty string.
338 // Get the I2P version name if I2P is installed.
339 val i2p: String = try {
340 // Check to see if the F-Droid flavor is installed. The newer `getPackageInfo()` may be used once the minimum API >= 33.
341 @Suppress("DEPRECATION")
342 requireContext().getString(R.string.fdroid_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName)
343 } catch (exception: PackageManager.NameNotFoundException) { // The F-Droid flavor is not installed.
345 // Check to see if the F-Droid flavor is installed. The newer `getPackageInfo()` may be used once the minimum API >= 33.
346 @Suppress("DEPRECATION")
347 requireContext().getString(R.string.google_play_flavor, requireContext().packageManager.getPackageInfo("net.i2p.android", 0).versionName)
348 } catch (exception: PackageManager.NameNotFoundException) { // The Google Play flavor is not installed either.
349 // Store an empty string.
354 // Get the OpenKeychain version name if it is installed.
355 val openKeychain: String = try {
356 // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33.
357 @Suppress("DEPRECATION")
358 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
359 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
360 // Store an empty string.
364 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
365 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
366 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
367 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
368 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
369 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
370 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
371 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
372 val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
373 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
374 val easyListStringBuilder = SpannableStringBuilder(easyListLabel + filterListsVersions[0])
375 val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + filterListsVersions[1])
376 val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + filterListsVersions[2])
377 val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + filterListsVersions[3])
378 val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + filterListsVersions[4])
379 val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + filterListsVersions[5])
381 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
382 blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
384 // Set the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
385 brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
386 manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
387 modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
388 deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
389 bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
390 androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
391 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
392 kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
393 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
394 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
395 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
396 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
397 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
398 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
399 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
401 // Display the strings in the text boxes.
402 versionTextView.text = version
403 brandTextView.text = brandStringBuilder
404 manufacturerTextView.text = manufacturerStringBuilder
405 modelTextView.text = modelStringBuilder
406 deviceTextView.text = deviceStringBuilder
407 bootloaderTextView.text = bootloaderStringBuilder
408 androidTextView.text = androidStringBuilder
409 buildTextView.text = buildStringBuilder
410 kernelTextView.text = kernelStringBuilder
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
439 val securityPatchLabel = getString(R.string.security_patch)
441 // Get the security patch version.
442 val securityPatch = Build.VERSION.SECURITY_PATCH
444 // Create a spannable string builder.
445 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
447 // Set the span to display the security patch version in blue.
448 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
450 // Display the string in the text view.
451 securityPatchTextView.text = securityPatchStringBuilder
453 // Create the WebView provider label.
454 val webViewProviderLabel = getString(R.string.webview_provider)
456 // Get the current WebView package info.
457 val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
459 // Get the WebView provider name.
460 val webViewPackageName = webViewPackageInfo.packageName
462 // Create the spannable string builder.
463 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
465 // Apply the coloration.
466 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
468 // Display the WebView provider.
469 webViewProviderTextView.text = webViewProviderStringBuilder
471 // Only populate the Orbot text view if it is installed.
472 if (orbot.isNotEmpty()) {
474 val orbotLabel = getString(R.string.orbot)
476 // Create a spannable string builder.
477 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
479 // Set the span to display the Orbot version.
480 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
482 // Display the string in the text view.
483 orbotTextView.text = orbotStringBuilder
484 } else { // Orbot is not installed.
485 // Hide the Orbot text view.
486 orbotTextView.visibility = View.GONE
489 // Only populate the I2P text view if it is installed.
490 if (i2p.isNotEmpty()) {
492 val i2pLabel = getString(R.string.i2p)
494 // Create a spannable string builder.
495 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
497 // Set the span to display the I2P version.
498 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
500 // Display the string in the text view.
501 i2pTextView.text = i2pStringBuilder
502 } else { // I2P is not installed.
503 // Hide the I2P text view.
504 i2pTextView.visibility = View.GONE
507 // Only populate the OpenKeychain text view if it is installed.
508 if (openKeychain.isNotEmpty()) {
510 val openKeychainLabel = getString(R.string.openkeychain)
512 // Create a spannable string builder.
513 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
515 // Set the span to display the OpenKeychain version.
516 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
518 // Display the string in the text view.
519 openKeychainTextView.text = openKeychainStringBuilder
520 } else { //OpenKeychain is not installed.
521 // Hide the OpenKeychain text view.
522 openKeychainTextView.visibility = View.GONE
525 // Display the package signature.
527 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
528 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
529 @Suppress("DEPRECATION")
530 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
533 // Convert the signature to a byte array input stream.
534 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
536 // Display the certificate information on the screen.
538 // Instantiate a certificate factory.
539 val certificateFactory = CertificateFactory.getInstance("X509")
541 // Generate an X509 certificate.
542 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
544 // Store the individual sections of the certificate.
545 val issuerDNPrincipal = x509Certificate.issuerDN
546 val subjectDNPrincipal = x509Certificate.subjectDN
547 val startDate = x509Certificate.notBefore
548 val endDate = x509Certificate.notAfter
549 val certificateVersion = x509Certificate.version
550 val serialNumberBigInteger = x509Certificate.serialNumber
551 val signatureAlgorithmNameString = x509Certificate.sigAlgName
553 // Create a spannable string builder for each text view that needs multiple colors of text.
554 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
555 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
556 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
557 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
558 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
559 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
560 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
562 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
563 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
564 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
565 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
566 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
567 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
568 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
569 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
571 // Display the strings in the text boxes.
572 certificateIssuerDnTextView.text = issuerDNStringBuilder
573 certificateSubjectDnTextView.text = subjectDNStringBuilder
574 certificateStartDateTextView.text = startDateStringBuilder
575 certificateEndDateTextView.text = endDataStringBuilder
576 certificateVersionTextView.text = certificateVersionStringBuilder
577 certificateSerialNumberTextView.text = serialNumberStringBuilder
578 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
579 } catch (certificateException: CertificateException) {
580 // Do nothing if there is a certificate error.
583 // Get a handle for the runtime.
584 runtime = Runtime.getRuntime()
586 // Get a handle for the activity manager.
587 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
589 // Instantiate a memory info variable.
590 memoryInfo = ActivityManager.MemoryInfo()
592 // Define a number format.
593 numberFormat = NumberFormat.getInstance()
595 // Set the minimum and maximum number of fraction digits.
596 numberFormat.minimumFractionDigits = 2
597 numberFormat.maximumFractionDigits = 2
599 // Update the memory usage.
600 updateMemoryUsage(requireActivity())
601 } catch (e: PackageManager.NameNotFoundException) {
602 // Do nothing if the package manager says Privacy Browser isn't installed.
605 // Scroll the tab if the saved instance state is not null.
606 if (savedInstanceState != null) {
607 aboutVersionLayout.post {
608 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
609 aboutVersionLayout.scrollY = savedInstanceState.getInt("scroll_y")
613 // Return the tab layout.
614 return aboutVersionLayout
617 override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
618 // Inflate the about version menu.
619 menuInflater.inflate(R.menu.about_version_options_menu, menu)
621 // Run the default commands.
622 super.onCreateOptionsMenu(menu, menuInflater)
625 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
626 // Run the appropriate commands.
627 when (menuItem.itemId) {
628 R.id.copy -> { // Copy.
629 // Get the about version string.
630 val aboutVersionString = getAboutVersionString()
632 // Get a handle for the clipboard manager.
633 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
635 // Save the about version string in a clip data.
636 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
638 // Place the clip data on the clipboard.
639 clipboardManager.setPrimaryClip(aboutVersionClipData)
641 // Display a snackbar.
642 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
644 // Consume the event.
648 R.id.share -> { // Share.
649 // Get the about version string.
650 val aboutString = getAboutVersionString()
652 // Create an email intent.
653 val emailIntent = Intent(Intent.ACTION_SEND)
655 // Add the about version string to the intent.
656 emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
658 // Set the MIME type.
659 emailIntent.type = "text/plain"
661 // Set the intent to open in a new task.
662 emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
665 startActivity(Intent.createChooser(emailIntent, getString(R.string.share)))
667 // Consume the event.
671 R.id.save_text -> { // Save text.
672 // Open the file picker.
673 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
675 // Consume the event.
679 R.id.save_image -> { // Save image.
680 // Open the file picker.
681 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
683 // Consume the event.
686 else -> { // The home button was selected.
687 // Run the parents class on return.
688 return super.onOptionsItemSelected(menuItem)
693 override fun onSaveInstanceState(savedInstanceState: Bundle) {
694 // Run the default commands.
695 super.onSaveInstanceState(savedInstanceState)
697 // Save the scroll positions.
698 savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
699 savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
702 override fun onPause() {
703 // Run the default commands.
706 // Pause the updating of the memory usage.
707 updateMemoryUsageBoolean = false
710 override fun onResume() {
711 // Run the default commands.
714 // Resume the updating of the memory usage.
715 updateMemoryUsageBoolean = true
718 private fun updateMemoryUsage(activity: Activity) {
720 // Update the memory usage if enabled.
721 if (updateMemoryUsageBoolean) {
722 // Populate the memory info variable.
723 activityManager.getMemoryInfo(memoryInfo)
725 // Get the app memory information.
726 val appAvailableMemoryLong = runtime.freeMemory()
727 val appTotalMemoryLong = runtime.totalMemory()
728 val appMaximumMemoryLong = runtime.maxMemory()
730 // Calculate the app consumed memory.
731 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
733 // Get the system memory information.
734 val systemTotalMemoryLong = memoryInfo.totalMem
735 val systemAvailableMemoryLong = memoryInfo.availMem
737 // Calculate the system consumed memory.
738 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
740 // Convert the memory information into mebibytes.
741 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
742 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
743 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
744 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
745 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
746 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
747 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
749 // Get the mebibyte string.
750 val mebibyte = getString(R.string.mebibyte)
752 // Calculate the mebibyte length.
753 val mebibyteLength = mebibyte.length
755 // Create spannable string builders.
756 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
757 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
758 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
759 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
760 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
761 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
762 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
764 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
765 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
766 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
767 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
768 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
769 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
770 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
771 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
773 // Display the string in the text boxes.
774 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
775 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
776 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
777 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
778 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
779 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
780 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
783 // Schedule another memory update if the activity has not been destroyed.
784 if (!activity.isDestroyed) {
785 // Create a handler to update the memory usage.
786 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
788 // Create a runnable to update the memory usage.
789 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
791 // Update the memory usage after 1000 milliseconds
792 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
794 } catch (exception: Exception) {
799 private fun getAboutVersionString(): String {
800 // Initialize an about version string builder.
801 val aboutVersionStringBuilder = StringBuilder()
803 // Populate the about version string builder.
804 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
805 aboutVersionStringBuilder.append("\n")
806 aboutVersionStringBuilder.append(versionTextView.text)
807 aboutVersionStringBuilder.append("\n\n")
808 aboutVersionStringBuilder.append(hardwareTextView.text)
809 aboutVersionStringBuilder.append("\n")
810 aboutVersionStringBuilder.append(brandTextView.text)
811 aboutVersionStringBuilder.append("\n")
812 aboutVersionStringBuilder.append(manufacturerTextView.text)
813 aboutVersionStringBuilder.append("\n")
814 aboutVersionStringBuilder.append(modelTextView.text)
815 aboutVersionStringBuilder.append("\n")
816 aboutVersionStringBuilder.append(deviceTextView.text)
817 aboutVersionStringBuilder.append("\n")
818 aboutVersionStringBuilder.append(bootloaderTextView.text)
819 aboutVersionStringBuilder.append("\n")
820 if (radioTextView.visibility == View.VISIBLE) {
821 aboutVersionStringBuilder.append(radioTextView.text)
822 aboutVersionStringBuilder.append("\n")
824 aboutVersionStringBuilder.append("\n")
825 aboutVersionStringBuilder.append(softwareTextView.text)
826 aboutVersionStringBuilder.append("\n")
827 aboutVersionStringBuilder.append(androidTextView.text)
828 aboutVersionStringBuilder.append("\n")
829 if (securityPatchTextView.visibility == View.VISIBLE) {
830 aboutVersionStringBuilder.append(securityPatchTextView.text)
831 aboutVersionStringBuilder.append("\n")
833 aboutVersionStringBuilder.append(buildTextView.text)
834 aboutVersionStringBuilder.append("\n")
835 aboutVersionStringBuilder.append(kernelTextView.text)
836 aboutVersionStringBuilder.append("\n")
837 if (webViewProviderTextView.visibility == View.VISIBLE) {
838 aboutVersionStringBuilder.append(webViewProviderTextView.text)
839 aboutVersionStringBuilder.append("\n")
841 aboutVersionStringBuilder.append(webViewVersionTextView.text)
842 aboutVersionStringBuilder.append("\n")
843 if (orbotTextView.visibility == View.VISIBLE) {
844 aboutVersionStringBuilder.append(orbotTextView.text)
845 aboutVersionStringBuilder.append("\n")
847 if (i2pTextView.visibility == View.VISIBLE) {
848 aboutVersionStringBuilder.append(i2pTextView.text)
849 aboutVersionStringBuilder.append("\n")
851 if (openKeychainTextView.visibility == View.VISIBLE) {
852 aboutVersionStringBuilder.append(openKeychainTextView.text)
853 aboutVersionStringBuilder.append("\n")
855 aboutVersionStringBuilder.append("\n")
856 aboutVersionStringBuilder.append(memoryUsageTextView.text)
857 aboutVersionStringBuilder.append("\n")
858 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
859 aboutVersionStringBuilder.append("\n")
860 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
861 aboutVersionStringBuilder.append("\n")
862 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
863 aboutVersionStringBuilder.append("\n")
864 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
865 aboutVersionStringBuilder.append("\n")
866 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
867 aboutVersionStringBuilder.append("\n")
868 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
869 aboutVersionStringBuilder.append("\n")
870 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
871 aboutVersionStringBuilder.append("\n\n")
872 aboutVersionStringBuilder.append(filterListsTextView.text)
873 aboutVersionStringBuilder.append("\n")
874 aboutVersionStringBuilder.append(easyListTextView.text)
875 aboutVersionStringBuilder.append("\n")
876 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
877 aboutVersionStringBuilder.append("\n")
878 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
879 aboutVersionStringBuilder.append("\n")
880 aboutVersionStringBuilder.append(fanboySocialTextView.text)
881 aboutVersionStringBuilder.append("\n")
882 aboutVersionStringBuilder.append(ultraListTextView.text)
883 aboutVersionStringBuilder.append("\n")
884 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
885 aboutVersionStringBuilder.append("\n\n")
886 aboutVersionStringBuilder.append(packageSignatureTextView.text)
887 aboutVersionStringBuilder.append("\n")
888 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
889 aboutVersionStringBuilder.append("\n")
890 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
891 aboutVersionStringBuilder.append("\n")
892 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
893 aboutVersionStringBuilder.append("\n")
894 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
895 aboutVersionStringBuilder.append("\n")
896 aboutVersionStringBuilder.append(certificateVersionTextView.text)
897 aboutVersionStringBuilder.append("\n")
898 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
899 aboutVersionStringBuilder.append("\n")
900 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
902 // Return the string.
903 return aboutVersionStringBuilder.toString()