2 * Copyright © 2016-2022 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.net.Uri
31 import android.os.Build
32 import android.os.Bundle
33 import android.os.Handler
34 import android.os.Looper
35 import android.provider.OpenableColumns
36 import android.text.SpannableStringBuilder
37 import android.text.Spanned
38 import android.text.style.ForegroundColorSpan
39 import android.view.LayoutInflater
40 import android.view.Menu
41 import android.view.MenuInflater
42 import android.view.MenuItem
43 import android.view.View
44 import android.view.ViewGroup
45 import android.webkit.WebView
46 import android.widget.TextView
48 import androidx.activity.result.contract.ActivityResultContracts
49 import androidx.fragment.app.Fragment
50 import androidx.webkit.WebViewCompat
52 import com.google.android.material.snackbar.Snackbar
54 import com.stoutner.privacybrowser.R
55 import com.stoutner.privacybrowser.BuildConfig
56 import com.stoutner.privacybrowser.asynctasks.SaveAboutVersionImage
58 import java.io.ByteArrayInputStream
59 import java.io.InputStream
60 import java.lang.Exception
61 import java.nio.charset.StandardCharsets
62 import java.security.cert.CertificateException
63 import java.security.cert.CertificateFactory
64 import java.security.cert.X509Certificate
65 import java.text.DateFormat
66 import java.text.NumberFormat
68 import kotlin.text.StringBuilder
70 // Define the class constants.
71 private const val BLOCKLIST_VERSIONS = "blocklist_versions"
72 private const val MEBIBYTE = 1048576
74 class AboutVersionFragment : Fragment() {
75 // Define the class variables.
76 private var updateMemoryUsageBoolean = true
78 // Declare the class variables.
79 private lateinit var blocklistVersions: Array<String>
80 private lateinit var aboutVersionLayout: View
81 private lateinit var appConsumedMemoryLabel: String
82 private lateinit var appAvailableMemoryLabel: String
83 private lateinit var appTotalMemoryLabel: String
84 private lateinit var appMaximumMemoryLabel: String
85 private lateinit var systemConsumedMemoryLabel: String
86 private lateinit var systemAvailableMemoryLabel: String
87 private lateinit var systemTotalMemoryLabel: String
88 private lateinit var runtime: Runtime
89 private lateinit var activityManager: ActivityManager
90 private lateinit var memoryInfo: ActivityManager.MemoryInfo
91 private lateinit var numberFormat: NumberFormat
92 private lateinit var blueColorSpan: ForegroundColorSpan
94 // Declare the class views.
95 private lateinit var privacyBrowserTextView: TextView
96 private lateinit var versionTextView: TextView
97 private lateinit var hardwareTextView: TextView
98 private lateinit var brandTextView: TextView
99 private lateinit var manufacturerTextView: TextView
100 private lateinit var modelTextView: TextView
101 private lateinit var deviceTextView: TextView
102 private lateinit var bootloaderTextView: TextView
103 private lateinit var radioTextView: TextView
104 private lateinit var softwareTextView: TextView
105 private lateinit var androidTextView: TextView
106 private lateinit var securityPatchTextView: TextView
107 private lateinit var buildTextView: TextView
108 private lateinit var kernelTextView: TextView
109 private lateinit var webViewProviderTextView: TextView
110 private lateinit var webViewVersionTextView: TextView
111 private lateinit var orbotTextView: TextView
112 private lateinit var i2pTextView: TextView
113 private lateinit var openKeychainTextView: TextView
114 private lateinit var memoryUsageTextView: TextView
115 private lateinit var appConsumedMemoryTextView: TextView
116 private lateinit var appAvailableMemoryTextView: TextView
117 private lateinit var appTotalMemoryTextView: TextView
118 private lateinit var appMaximumMemoryTextView: TextView
119 private lateinit var systemConsumedMemoryTextView: TextView
120 private lateinit var systemAvailableMemoryTextView: TextView
121 private lateinit var systemTotalMemoryTextView: TextView
122 private lateinit var blocklistsTextView: TextView
123 private lateinit var easyListTextView: TextView
124 private lateinit var easyPrivacyTextView: TextView
125 private lateinit var fanboyAnnoyanceTextView: TextView
126 private lateinit var fanboySocialTextView: TextView
127 private lateinit var ultraListTextView: TextView
128 private lateinit var ultraPrivacyTextView: TextView
129 private lateinit var packageSignatureTextView: TextView
130 private lateinit var certificateIssuerDnTextView: TextView
131 private lateinit var certificateSubjectDnTextView: TextView
132 private lateinit var certificateStartDateTextView: TextView
133 private lateinit var certificateEndDateTextView: TextView
134 private lateinit var certificateVersionTextView: TextView
135 private lateinit var certificateSerialNumberTextView: TextView
136 private lateinit var certificateSignatureAlgorithmTextView: TextView
139 fun createTab(blocklistVersions: Array<String>): AboutVersionFragment {
140 // Create an arguments bundle.
141 val argumentsBundle = Bundle()
143 // Store the arguments in the bundle.
144 argumentsBundle.putStringArray(BLOCKLIST_VERSIONS, blocklistVersions)
146 // Create a new instance of the tab fragment.
147 val aboutVersionFragment = AboutVersionFragment()
149 // Add the arguments bundle to the fragment.
150 aboutVersionFragment.arguments = argumentsBundle
152 // Return the new fragment.
153 return aboutVersionFragment
157 // Define the save about version text activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
158 private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri: Uri? ->
159 // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
160 if (fileUri != null) {
162 // Get the about version string.
163 val aboutVersionString = getAboutVersionString()
165 // Open an output stream.
166 val outputStream = requireActivity().contentResolver.openOutputStream(fileUri)!!
168 // Write the about version string to the output stream.
169 outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
171 // Close the output stream.
174 // Initialize the file name string from the file URI last path segment.
175 var fileNameString = fileUri.lastPathSegment
177 // Query the exact file name if the API >= 26.
178 if (Build.VERSION.SDK_INT >= 26) {
179 // Get a cursor from the content resolver.
180 val contentResolverCursor = requireActivity().contentResolver.query(fileUri, null, null, null)!!
182 // Move to the first row.
183 contentResolverCursor.moveToFirst()
185 // Get the file name from the cursor.
186 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
189 contentResolverCursor.close()
192 // Display a snackbar with the saved logcat information.
193 Snackbar.make(aboutVersionLayout, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
194 } catch (exception: Exception) {
195 // Display a snackbar with the error message.
196 Snackbar.make(aboutVersionLayout, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show()
201 // Define the save about version image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
202 private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { fileUri: Uri? ->
203 // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
204 if (fileUri != null) {
205 // Save the about version image.
206 SaveAboutVersionImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout)).execute()
210 override fun onCreate(savedInstanceState: Bundle?) {
211 // Run the default commands.
212 super.onCreate(savedInstanceState)
214 // Store the arguments in class variables.
215 blocklistVersions = requireArguments().getStringArray(BLOCKLIST_VERSIONS)!!
217 // Enable the options menu for this fragment.
218 setHasOptionsMenu(true)
221 override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
222 // 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.
223 aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
225 // Get handles for the views.
226 privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
227 versionTextView = aboutVersionLayout.findViewById(R.id.version)
228 hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
229 brandTextView = aboutVersionLayout.findViewById(R.id.brand)
230 manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
231 modelTextView = aboutVersionLayout.findViewById(R.id.model)
232 deviceTextView = aboutVersionLayout.findViewById(R.id.device)
233 bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
234 radioTextView = aboutVersionLayout.findViewById(R.id.radio)
235 softwareTextView = aboutVersionLayout.findViewById(R.id.software)
236 androidTextView = aboutVersionLayout.findViewById(R.id.android)
237 securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
238 buildTextView = aboutVersionLayout.findViewById(R.id.build)
239 kernelTextView = aboutVersionLayout.findViewById(R.id.kernel)
240 webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
241 webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
242 orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
243 i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
244 openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
245 memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
246 appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
247 appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
248 appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
249 appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
250 systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
251 systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
252 systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
253 blocklistsTextView = aboutVersionLayout.findViewById(R.id.blocklists)
254 easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
255 easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
256 fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
257 fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
258 ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
259 ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
260 packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
261 certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
262 certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
263 certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
264 certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
265 certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
266 certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
267 certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
270 val version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + BuildConfig.VERSION_CODE + ")"
271 val brandLabel = getString(R.string.brand) + " "
272 val manufacturerLabel = getString(R.string.manufacturer) + " "
273 val modelLabel = getString(R.string.model) + " "
274 val deviceLabel = getString(R.string.device) + " "
275 val bootloaderLabel = getString(R.string.bootloader) + " "
276 val androidLabel = getString(R.string.android) + " "
277 val buildLabel = getString(R.string.build) + " "
278 val kernelLabel = getString(R.string.kernel) + " "
279 val webViewVersionLabel = getString(R.string.webview_version) + " "
280 appConsumedMemoryLabel = getString(R.string.app_consumed_memory) + " "
281 appAvailableMemoryLabel = getString(R.string.app_available_memory) + " "
282 appTotalMemoryLabel = getString(R.string.app_total_memory) + " "
283 appMaximumMemoryLabel = getString(R.string.app_maximum_memory) + " "
284 systemConsumedMemoryLabel = getString(R.string.system_consumed_memory) + " "
285 systemAvailableMemoryLabel = getString(R.string.system_available_memory) + " "
286 systemTotalMemoryLabel = getString(R.string.system_total_memory) + " "
287 val easyListLabel = getString(R.string.easylist_label) + " "
288 val easyPrivacyLabel = getString(R.string.easyprivacy_label) + " "
289 val fanboyAnnoyanceLabel = getString(R.string.fanboy_annoyance_label) + " "
290 val fanboySocialLabel = getString(R.string.fanboy_social_label) + " "
291 val ultraListLabel = getString(R.string.ultralist_label) + " "
292 val ultraPrivacyLabel = getString(R.string.ultraprivacy_label) + " "
293 val issuerDNLabel = getString(R.string.issuer_dn) + " "
294 val subjectDNLabel = getString(R.string.subject_dn) + " "
295 val startDateLabel = getString(R.string.start_date) + " "
296 val endDateLabel = getString(R.string.end_date) + " "
297 val certificateVersionLabel = getString(R.string.certificate_version) + " "
298 val serialNumberLabel = getString(R.string.serial_number) + " "
299 val signatureAlgorithmLabel = getString(R.string.signature_algorithm) + " "
301 // 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.
302 // Once the minimum API >= 26 this can be accomplished with the WebView package info.
303 val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false)
304 val tabLayoutWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
305 val userAgentString = tabLayoutWebView.settings.userAgentString
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 = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Build.VERSION.SDK_INT + ")"
315 val build = Build.DISPLAY
316 val kernel = System.getProperty("os.version")
318 // Get the WebView version, selecting the substring that begins after `Chrome/` and goes until the next ` `.
319 val webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")))
321 // Get the Orbot version name if Orbot is installed.
322 val orbot: String = try {
323 // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33.
324 @Suppress("DEPRECATION")
325 requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
326 } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed.
327 // Store an empty string.
331 // Get the I2P version name if I2P is installed.
332 val i2p: String = try {
333 // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33.
334 @Suppress("DEPRECATION")
335 requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName
336 } catch (exception: PackageManager.NameNotFoundException) { // I2P is not installed.
337 // Store an empty string.
341 // Get the OpenKeychain version name if it is installed.
342 val openKeychain: String = try {
343 // Store the version name. The newer `getPackageInfo()` may be used once the minimum API >= 33.
344 @Suppress("DEPRECATION")
345 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
346 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
347 // Store an empty string.
351 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
352 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
353 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
354 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
355 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
356 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
357 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
358 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
359 val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
360 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
361 val easyListStringBuilder = SpannableStringBuilder(easyListLabel + blocklistVersions[0])
362 val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1])
363 val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2])
364 val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3])
365 val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + blocklistVersions[4])
366 val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5])
368 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
369 blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
371 // Set the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
372 brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
373 manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
374 modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
375 deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
376 bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
377 androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
378 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
379 kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
380 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
381 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
382 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
383 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
384 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
385 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
386 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
388 // Display the strings in the text boxes.
389 versionTextView.text = version
390 brandTextView.text = brandStringBuilder
391 manufacturerTextView.text = manufacturerStringBuilder
392 modelTextView.text = modelStringBuilder
393 deviceTextView.text = deviceStringBuilder
394 bootloaderTextView.text = bootloaderStringBuilder
395 androidTextView.text = androidStringBuilder
396 buildTextView.text = buildStringBuilder
397 kernelTextView.text = kernelStringBuilder
398 webViewVersionTextView.text = webViewVersionStringBuilder
399 easyListTextView.text = easyListStringBuilder
400 easyPrivacyTextView.text = easyPrivacyStringBuilder
401 fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
402 fanboySocialTextView.text = fanboySocialStringBuilder
403 ultraListTextView.text = ultraListStringBuilder
404 ultraPrivacyTextView.text = ultraPrivacyStringBuilder
406 // Only populate the radio text view if there is a radio in the device.
407 // 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>
408 if (radio != null && radio.isNotEmpty()) {
410 val radioLabel = getString(R.string.radio) + " "
412 // Create a spannable string builder.
413 val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
415 // Set the span to display the radio in blue.
416 radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
418 // Display the string in the text view.
419 radioTextView.text = radioStringBuilder
420 } else { // This device does not have a radio.
421 // Hide the radio text view.
422 radioTextView.visibility = View.GONE
426 val securityPatchLabel = getString(R.string.security_patch) + " "
428 // Get the security patch version.
429 val securityPatch = Build.VERSION.SECURITY_PATCH
431 // Create a spannable string builder.
432 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
434 // Set the span to display the security patch version in blue.
435 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
437 // Display the string in the text view.
438 securityPatchTextView.text = securityPatchStringBuilder
440 // Create the WebView provider label.
441 val webViewProviderLabel = getString(R.string.webview_provider) + " "
443 // Get the current WebView package info.
444 val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
446 // Get the WebView provider name.
447 val webViewPackageName = webViewPackageInfo.packageName
449 // Create the spannable string builder.
450 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
452 // Apply the coloration.
453 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
455 // Display the WebView provider.
456 webViewProviderTextView.text = webViewProviderStringBuilder
458 // Only populate the Orbot text view if it is installed.
459 if (orbot.isNotEmpty()) {
461 val orbotLabel = getString(R.string.orbot) + " "
463 // Create a spannable string builder.
464 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
466 // Set the span to display the Orbot version.
467 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
469 // Display the string in the text view.
470 orbotTextView.text = orbotStringBuilder
471 } else { // Orbot is not installed.
472 // Hide the Orbot text view.
473 orbotTextView.visibility = View.GONE
476 // Only populate the I2P text view if it is installed.
477 if (i2p.isNotEmpty()) {
479 val i2pLabel = getString(R.string.i2p) + " "
481 // Create a spannable string builder.
482 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
484 // Set the span to display the I2P version.
485 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
487 // Display the string in the text view.
488 i2pTextView.text = i2pStringBuilder
489 } else { // I2P is not installed.
490 // Hide the I2P text view.
491 i2pTextView.visibility = View.GONE
494 // Only populate the OpenKeychain text view if it is installed.
495 if (openKeychain.isNotEmpty()) {
497 val openKeychainLabel = getString(R.string.openkeychain) + " "
499 // Create a spannable string builder.
500 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
502 // Set the span to display the OpenKeychain version.
503 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
505 // Display the string in the text view.
506 openKeychainTextView.text = openKeychainStringBuilder
507 } else { //OpenKeychain is not installed.
508 // Hide the OpenKeychain text view.
509 openKeychainTextView.visibility = View.GONE
512 // Display the package signature.
514 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
515 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead. Once the minimum API >= 33, the newer `getPackageInfo()` may be used.
516 @Suppress("DEPRECATION")
517 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
520 // Convert the signature to a byte array input stream.
521 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
523 // Display the certificate information on the screen.
525 // Instantiate a certificate factory.
526 val certificateFactory = CertificateFactory.getInstance("X509")
528 // Generate an X509 certificate.
529 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
531 // Store the individual sections of the certificate.
532 val issuerDNPrincipal = x509Certificate.issuerDN
533 val subjectDNPrincipal = x509Certificate.subjectDN
534 val startDate = x509Certificate.notBefore
535 val endDate = x509Certificate.notAfter
536 val certificateVersion = x509Certificate.version
537 val serialNumberBigInteger = x509Certificate.serialNumber
538 val signatureAlgorithmNameString = x509Certificate.sigAlgName
540 // Create a spannable string builder for each text view that needs multiple colors of text.
541 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
542 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
543 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
544 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
545 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
546 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
547 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
549 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
550 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
551 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
552 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
553 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
554 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
555 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
556 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
558 // Display the strings in the text boxes.
559 certificateIssuerDnTextView.text = issuerDNStringBuilder
560 certificateSubjectDnTextView.text = subjectDNStringBuilder
561 certificateStartDateTextView.text = startDateStringBuilder
562 certificateEndDateTextView.text = endDataStringBuilder
563 certificateVersionTextView.text = certificateVersionStringBuilder
564 certificateSerialNumberTextView.text = serialNumberStringBuilder
565 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
566 } catch (certificateException: CertificateException) {
567 // Do nothing if there is a certificate error.
570 // Get a handle for the runtime.
571 runtime = Runtime.getRuntime()
573 // Get a handle for the activity manager.
574 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
576 // Instantiate a memory info variable.
577 memoryInfo = ActivityManager.MemoryInfo()
579 // Define a number format.
580 numberFormat = NumberFormat.getInstance()
582 // Set the minimum and maximum number of fraction digits.
583 numberFormat.minimumFractionDigits = 2
584 numberFormat.maximumFractionDigits = 2
586 // Update the memory usage.
587 updateMemoryUsage(requireActivity())
588 } catch (e: PackageManager.NameNotFoundException) {
589 // Do nothing if the package manager says Privacy Browser isn't installed.
592 // Scroll the tab if the saved instance state is not null.
593 if (savedInstanceState != null) {
594 aboutVersionLayout.post {
595 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
596 aboutVersionLayout.scrollY = savedInstanceState.getInt("scroll_y")
600 // Return the tab layout.
601 return aboutVersionLayout
604 override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
605 // Inflate the about version menu.
606 menuInflater.inflate(R.menu.about_version_options_menu, menu)
608 // Run the default commands.
609 super.onCreateOptionsMenu(menu, menuInflater)
612 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
613 // Run the appropriate commands.
614 when (menuItem.itemId) {
615 R.id.copy -> { // Copy.
616 // Get the about version string.
617 val aboutVersionString = getAboutVersionString()
619 // Get a handle for the clipboard manager.
620 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
622 // Save the about version string in a clip data.
623 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
625 // Place the clip data on the clipboard.
626 clipboardManager.setPrimaryClip(aboutVersionClipData)
628 // Display a snackbar.
629 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
631 // Consume the event.
635 R.id.share -> { // Share.
636 // Get the about version string.
637 val aboutString = getAboutVersionString()
639 // Create an email intent.
640 val emailIntent = Intent(Intent.ACTION_SEND)
642 // Add the about version string to the intent.
643 emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
645 // Set the MIME type.
646 emailIntent.type = "text/plain"
648 // Set the intent to open in a new task.
649 emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
652 startActivity(Intent.createChooser(emailIntent, getString(R.string.share)))
654 // Consume the event.
658 R.id.save_text -> { // Save text.
659 // Open the file picker.
660 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
662 // Consume the event.
666 R.id.save_image -> { // Save image.
667 // Open the file picker.
668 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
670 // Consume the event.
673 else -> { // The home button was selected.
674 // Run the parents class on return.
675 return super.onOptionsItemSelected(menuItem)
680 override fun onSaveInstanceState(savedInstanceState: Bundle) {
681 // Run the default commands.
682 super.onSaveInstanceState(savedInstanceState)
684 // Save the scroll positions.
685 savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
686 savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
689 override fun onPause() {
690 // Run the default commands.
693 // Pause the updating of the memory usage.
694 updateMemoryUsageBoolean = false
697 override fun onResume() {
698 // Run the default commands.
701 // Resume the updating of the memory usage.
702 updateMemoryUsageBoolean = true
705 fun updateMemoryUsage(activity: Activity) {
707 // Update the memory usage if enabled.
708 if (updateMemoryUsageBoolean) {
709 // Populate the memory info variable.
710 activityManager.getMemoryInfo(memoryInfo)
712 // Get the app memory information.
713 val appAvailableMemoryLong = runtime.freeMemory()
714 val appTotalMemoryLong = runtime.totalMemory()
715 val appMaximumMemoryLong = runtime.maxMemory()
717 // Calculate the app consumed memory.
718 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
720 // Get the system memory information.
721 val systemTotalMemoryLong = memoryInfo.totalMem
722 val systemAvailableMemoryLong = memoryInfo.availMem
724 // Calculate the system consumed memory.
725 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
727 // Convert the memory information into mebibytes.
728 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
729 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
730 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
731 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
732 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
733 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
734 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
736 // Get the mebibyte string.
737 val mebibyte = getString(R.string.mebibyte)
739 // Calculate the mebibyte length.
740 val mebibyteLength = mebibyte.length
742 // Create spannable string builders.
743 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
744 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
745 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
746 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
747 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
748 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
749 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
751 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
752 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
753 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
754 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
755 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
756 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
757 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
758 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
760 // Display the string in the text boxes.
761 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
762 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
763 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
764 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
765 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
766 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
767 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
770 // Schedule another memory update if the activity has not been destroyed.
771 if (!activity.isDestroyed) {
772 // Create a handler to update the memory usage.
773 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
775 // Create a runnable to update the memory usage.
776 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
778 // Update the memory usage after 1000 milliseconds
779 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
781 } catch (exception: Exception) {
786 fun getAboutVersionString(): String {
787 // Initialize an about version string builder.
788 val aboutVersionStringBuilder = StringBuilder()
790 // Populate the about version string builder.
791 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
792 aboutVersionStringBuilder.append("\n")
793 aboutVersionStringBuilder.append(versionTextView.text)
794 aboutVersionStringBuilder.append("\n\n")
795 aboutVersionStringBuilder.append(hardwareTextView.text)
796 aboutVersionStringBuilder.append("\n")
797 aboutVersionStringBuilder.append(brandTextView.text)
798 aboutVersionStringBuilder.append("\n")
799 aboutVersionStringBuilder.append(manufacturerTextView.text)
800 aboutVersionStringBuilder.append("\n")
801 aboutVersionStringBuilder.append(modelTextView.text)
802 aboutVersionStringBuilder.append("\n")
803 aboutVersionStringBuilder.append(deviceTextView.text)
804 aboutVersionStringBuilder.append("\n")
805 aboutVersionStringBuilder.append(bootloaderTextView.text)
806 aboutVersionStringBuilder.append("\n")
807 if (radioTextView.visibility == View.VISIBLE) {
808 aboutVersionStringBuilder.append(radioTextView.text)
809 aboutVersionStringBuilder.append("\n")
811 aboutVersionStringBuilder.append("\n")
812 aboutVersionStringBuilder.append(softwareTextView.text)
813 aboutVersionStringBuilder.append("\n")
814 aboutVersionStringBuilder.append(androidTextView.text)
815 aboutVersionStringBuilder.append("\n")
816 if (securityPatchTextView.visibility == View.VISIBLE) {
817 aboutVersionStringBuilder.append(securityPatchTextView.text)
818 aboutVersionStringBuilder.append("\n")
820 aboutVersionStringBuilder.append(buildTextView.text)
821 aboutVersionStringBuilder.append("\n")
822 aboutVersionStringBuilder.append(kernelTextView.text)
823 aboutVersionStringBuilder.append("\n")
824 if (webViewProviderTextView.visibility == View.VISIBLE) {
825 aboutVersionStringBuilder.append(webViewProviderTextView.text)
826 aboutVersionStringBuilder.append("\n")
828 aboutVersionStringBuilder.append(webViewVersionTextView.text)
829 aboutVersionStringBuilder.append("\n")
830 if (orbotTextView.visibility == View.VISIBLE) {
831 aboutVersionStringBuilder.append(orbotTextView.text)
832 aboutVersionStringBuilder.append("\n")
834 if (i2pTextView.visibility == View.VISIBLE) {
835 aboutVersionStringBuilder.append(i2pTextView.text)
836 aboutVersionStringBuilder.append("\n")
838 if (openKeychainTextView.visibility == View.VISIBLE) {
839 aboutVersionStringBuilder.append(openKeychainTextView.text)
840 aboutVersionStringBuilder.append("\n")
842 aboutVersionStringBuilder.append("\n")
843 aboutVersionStringBuilder.append(memoryUsageTextView.text)
844 aboutVersionStringBuilder.append("\n")
845 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
846 aboutVersionStringBuilder.append("\n")
847 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
848 aboutVersionStringBuilder.append("\n")
849 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
850 aboutVersionStringBuilder.append("\n")
851 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
852 aboutVersionStringBuilder.append("\n")
853 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
854 aboutVersionStringBuilder.append("\n")
855 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
856 aboutVersionStringBuilder.append("\n")
857 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
858 aboutVersionStringBuilder.append("\n\n")
859 aboutVersionStringBuilder.append(blocklistsTextView.text)
860 aboutVersionStringBuilder.append("\n")
861 aboutVersionStringBuilder.append(easyListTextView.text)
862 aboutVersionStringBuilder.append("\n")
863 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
864 aboutVersionStringBuilder.append("\n")
865 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
866 aboutVersionStringBuilder.append("\n")
867 aboutVersionStringBuilder.append(fanboySocialTextView.text)
868 aboutVersionStringBuilder.append("\n")
869 aboutVersionStringBuilder.append(ultraListTextView.text)
870 aboutVersionStringBuilder.append("\n")
871 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
872 aboutVersionStringBuilder.append("\n\n")
873 aboutVersionStringBuilder.append(packageSignatureTextView.text)
874 aboutVersionStringBuilder.append("\n")
875 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
876 aboutVersionStringBuilder.append("\n")
877 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
878 aboutVersionStringBuilder.append("\n")
879 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
880 aboutVersionStringBuilder.append("\n")
881 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
882 aboutVersionStringBuilder.append("\n")
883 aboutVersionStringBuilder.append(certificateVersionTextView.text)
884 aboutVersionStringBuilder.append("\n")
885 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
886 aboutVersionStringBuilder.append("\n")
887 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
889 // Return the string.
890 return aboutVersionStringBuilder.toString()