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()) { 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()) { 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.
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 // Store the version name.
333 requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName
334 } catch (exception: PackageManager.NameNotFoundException) { // I2P is not installed.
335 // Store an empty string.
339 // Get the OpenKeychain version name if it is installed.
340 val openKeychain: String = try {
341 // Store the version name.
342 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
343 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
344 // Store an empty string.
348 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
349 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
350 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
351 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
352 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
353 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
354 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
355 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
356 val kernelStringBuilder = SpannableStringBuilder(kernelLabel + kernel)
357 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
358 val easyListStringBuilder = SpannableStringBuilder(easyListLabel + blocklistVersions[0])
359 val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1])
360 val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2])
361 val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3])
362 val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + blocklistVersions[4])
363 val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5])
365 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
366 blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
368 // Set the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
369 brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
370 manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
371 modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
372 deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
373 bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
374 androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
375 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
376 kernelStringBuilder.setSpan(blueColorSpan, kernelLabel.length, kernelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
377 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
378 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
379 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
380 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
381 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
382 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
383 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
385 // Display the strings in the text boxes.
386 versionTextView.text = version
387 brandTextView.text = brandStringBuilder
388 manufacturerTextView.text = manufacturerStringBuilder
389 modelTextView.text = modelStringBuilder
390 deviceTextView.text = deviceStringBuilder
391 bootloaderTextView.text = bootloaderStringBuilder
392 androidTextView.text = androidStringBuilder
393 buildTextView.text = buildStringBuilder
394 kernelTextView.text = kernelStringBuilder
395 webViewVersionTextView.text = webViewVersionStringBuilder
396 easyListTextView.text = easyListStringBuilder
397 easyPrivacyTextView.text = easyPrivacyStringBuilder
398 fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
399 fanboySocialTextView.text = fanboySocialStringBuilder
400 ultraListTextView.text = ultraListStringBuilder
401 ultraPrivacyTextView.text = ultraPrivacyStringBuilder
403 // Only populate the radio text view if there is a radio in the device.
404 // 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>
405 if (radio != null && radio.isNotEmpty()) {
407 val radioLabel = getString(R.string.radio) + " "
409 // Create a spannable string builder.
410 val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
412 // Set the span to display the radio in blue.
413 radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
415 // Display the string in the text view.
416 radioTextView.text = radioStringBuilder
417 } else { // This device does not have a radio.
418 // Hide the radio text view.
419 radioTextView.visibility = View.GONE
423 val securityPatchLabel = getString(R.string.security_patch) + " "
425 // Get the security patch version.
426 val securityPatch = Build.VERSION.SECURITY_PATCH
428 // Create a spannable string builder.
429 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
431 // Set the span to display the security patch version in blue.
432 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
434 // Display the string in the text view.
435 securityPatchTextView.text = securityPatchStringBuilder
437 // Create the WebView provider label.
438 val webViewProviderLabel = getString(R.string.webview_provider) + " "
440 // Get the current WebView package info.
441 val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
443 // Get the WebView provider name.
444 val webViewPackageName = webViewPackageInfo.packageName
446 // Create the spannable string builder.
447 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
449 // Apply the coloration.
450 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
452 // Display the WebView provider.
453 webViewProviderTextView.text = webViewProviderStringBuilder
455 // Only populate the Orbot text view if it is installed.
456 if (orbot.isNotEmpty()) {
458 val orbotLabel = getString(R.string.orbot) + " "
460 // Create a spannable string builder.
461 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
463 // Set the span to display the Orbot version.
464 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
466 // Display the string in the text view.
467 orbotTextView.text = orbotStringBuilder
468 } else { // Orbot is not installed.
469 // Hide the Orbot text view.
470 orbotTextView.visibility = View.GONE
473 // Only populate the I2P text view if it is installed.
474 if (i2p.isNotEmpty()) {
476 val i2pLabel = getString(R.string.i2p) + " "
478 // Create a spannable string builder.
479 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
481 // Set the span to display the I2P version.
482 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
484 // Display the string in the text view.
485 i2pTextView.text = i2pStringBuilder
486 } else { // I2P is not installed.
487 // Hide the I2P text view.
488 i2pTextView.visibility = View.GONE
491 // Only populate the OpenKeychain text view if it is installed.
492 if (openKeychain.isNotEmpty()) {
494 val openKeychainLabel = getString(R.string.openkeychain) + " "
496 // Create a spannable string builder.
497 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
499 // Set the span to display the OpenKeychain version.
500 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
502 // Display the string in the text view.
503 openKeychainTextView.text = openKeychainStringBuilder
504 } else { //OpenKeychain is not installed.
505 // Hide the OpenKeychain text view.
506 openKeychainTextView.visibility = View.GONE
509 // Display the package signature.
511 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
512 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead.
513 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
516 // Convert the signature to a byte array input stream.
517 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
519 // Display the certificate information on the screen.
521 // Instantiate a certificate factory.
522 val certificateFactory = CertificateFactory.getInstance("X509")
524 // Generate an X509 certificate.
525 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
527 // Store the individual sections of the certificate.
528 val issuerDNPrincipal = x509Certificate.issuerDN
529 val subjectDNPrincipal = x509Certificate.subjectDN
530 val startDate = x509Certificate.notBefore
531 val endDate = x509Certificate.notAfter
532 val certificateVersion = x509Certificate.version
533 val serialNumberBigInteger = x509Certificate.serialNumber
534 val signatureAlgorithmNameString = x509Certificate.sigAlgName
536 // Create a spannable string builder for each text view that needs multiple colors of text.
537 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
538 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
539 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
540 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
541 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
542 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
543 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
545 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
546 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
547 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
548 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
549 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
550 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
551 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
552 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
554 // Display the strings in the text boxes.
555 certificateIssuerDnTextView.text = issuerDNStringBuilder
556 certificateSubjectDnTextView.text = subjectDNStringBuilder
557 certificateStartDateTextView.text = startDateStringBuilder
558 certificateEndDateTextView.text = endDataStringBuilder
559 certificateVersionTextView.text = certificateVersionStringBuilder
560 certificateSerialNumberTextView.text = serialNumberStringBuilder
561 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
562 } catch (certificateException: CertificateException) {
563 // Do nothing if there is a certificate error.
566 // Get a handle for the runtime.
567 runtime = Runtime.getRuntime()
569 // Get a handle for the activity manager.
570 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
572 // Instantiate a memory info variable.
573 memoryInfo = ActivityManager.MemoryInfo()
575 // Define a number format.
576 numberFormat = NumberFormat.getInstance()
578 // Set the minimum and maximum number of fraction digits.
579 numberFormat.minimumFractionDigits = 2
580 numberFormat.maximumFractionDigits = 2
582 // Update the memory usage.
583 updateMemoryUsage(requireActivity())
584 } catch (e: PackageManager.NameNotFoundException) {
585 // Do nothing if the package manager says Privacy Browser isn't installed.
588 // Scroll the tab if the saved instance state is not null.
589 if (savedInstanceState != null) {
590 aboutVersionLayout.post {
591 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
592 aboutVersionLayout.scrollY = savedInstanceState.getInt("scroll_y")
596 // Return the tab layout.
597 return aboutVersionLayout
600 override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
601 // Inflate the about version menu.
602 menuInflater.inflate(R.menu.about_version_options_menu, menu)
604 // Run the default commands.
605 super.onCreateOptionsMenu(menu, menuInflater)
608 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
609 // Run the appropriate commands.
610 when (menuItem.itemId) {
611 R.id.copy -> { // Copy.
612 // Get the about version string.
613 val aboutVersionString = getAboutVersionString()
615 // Get a handle for the clipboard manager.
616 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
618 // Save the about version string in a clip data.
619 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
621 // Place the clip data on the clipboard.
622 clipboardManager.setPrimaryClip(aboutVersionClipData)
624 // Display a snackbar.
625 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
627 // Consume the event.
631 R.id.share -> { // Share.
632 // Get the about version string.
633 val aboutString = getAboutVersionString()
635 // Create an email intent.
636 val emailIntent = Intent(Intent.ACTION_SEND)
638 // Add the about version string to the intent.
639 emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
641 // Set the MIME type.
642 emailIntent.type = "text/plain"
644 // Set the intent to open in a new task.
645 emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
648 startActivity(Intent.createChooser(emailIntent, getString(R.string.share)))
650 // Consume the event.
654 R.id.save_text -> { // Save text.
655 // Open the file picker.
656 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
658 // Consume the event.
662 R.id.save_image -> { // Save image.
663 // Open the file picker.
664 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
666 // Consume the event.
669 else -> { // The home button was selected.
670 // Run the parents class on return.
671 return super.onOptionsItemSelected(menuItem)
676 override fun onSaveInstanceState(savedInstanceState: Bundle) {
677 // Run the default commands.
678 super.onSaveInstanceState(savedInstanceState)
680 // Save the scroll positions.
681 savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
682 savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
685 override fun onPause() {
686 // Run the default commands.
689 // Pause the updating of the memory usage.
690 updateMemoryUsageBoolean = false
693 override fun onResume() {
694 // Run the default commands.
697 // Resume the updating of the memory usage.
698 updateMemoryUsageBoolean = true
701 fun updateMemoryUsage(activity: Activity) {
703 // Update the memory usage if enabled.
704 if (updateMemoryUsageBoolean) {
705 // Populate the memory info variable.
706 activityManager.getMemoryInfo(memoryInfo)
708 // Get the app memory information.
709 val appAvailableMemoryLong = runtime.freeMemory()
710 val appTotalMemoryLong = runtime.totalMemory()
711 val appMaximumMemoryLong = runtime.maxMemory()
713 // Calculate the app consumed memory.
714 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
716 // Get the system memory information.
717 val systemTotalMemoryLong = memoryInfo.totalMem
718 val systemAvailableMemoryLong = memoryInfo.availMem
720 // Calculate the system consumed memory.
721 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
723 // Convert the memory information into mebibytes.
724 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
725 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
726 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
727 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
728 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
729 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
730 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
732 // Get the mebibyte string.
733 val mebibyte = getString(R.string.mebibyte)
735 // Calculate the mebibyte length.
736 val mebibyteLength = mebibyte.length
738 // Create spannable string builders.
739 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
740 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
741 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
742 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
743 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
744 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
745 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
747 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
748 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
749 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
750 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
751 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
752 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
753 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
754 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
756 // Display the string in the text boxes.
757 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
758 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
759 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
760 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
761 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
762 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
763 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
766 // Schedule another memory update if the activity has not been destroyed.
767 if (!activity.isDestroyed) {
768 // Create a handler to update the memory usage.
769 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
771 // Create a runnable to update the memory usage.
772 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
774 // Update the memory usage after 1000 milliseconds
775 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
777 } catch (exception: Exception) {
782 fun getAboutVersionString(): String {
783 // Initialize an about version string builder.
784 val aboutVersionStringBuilder = StringBuilder()
786 // Populate the about version string builder.
787 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
788 aboutVersionStringBuilder.append("\n")
789 aboutVersionStringBuilder.append(versionTextView.text)
790 aboutVersionStringBuilder.append("\n\n")
791 aboutVersionStringBuilder.append(hardwareTextView.text)
792 aboutVersionStringBuilder.append("\n")
793 aboutVersionStringBuilder.append(brandTextView.text)
794 aboutVersionStringBuilder.append("\n")
795 aboutVersionStringBuilder.append(manufacturerTextView.text)
796 aboutVersionStringBuilder.append("\n")
797 aboutVersionStringBuilder.append(modelTextView.text)
798 aboutVersionStringBuilder.append("\n")
799 aboutVersionStringBuilder.append(deviceTextView.text)
800 aboutVersionStringBuilder.append("\n")
801 aboutVersionStringBuilder.append(bootloaderTextView.text)
802 aboutVersionStringBuilder.append("\n")
803 if (radioTextView.visibility == View.VISIBLE) {
804 aboutVersionStringBuilder.append(radioTextView.text)
805 aboutVersionStringBuilder.append("\n")
807 aboutVersionStringBuilder.append("\n")
808 aboutVersionStringBuilder.append(softwareTextView.text)
809 aboutVersionStringBuilder.append("\n")
810 aboutVersionStringBuilder.append(androidTextView.text)
811 aboutVersionStringBuilder.append("\n")
812 if (securityPatchTextView.visibility == View.VISIBLE) {
813 aboutVersionStringBuilder.append(securityPatchTextView.text)
814 aboutVersionStringBuilder.append("\n")
816 aboutVersionStringBuilder.append(buildTextView.text)
817 aboutVersionStringBuilder.append("\n")
818 aboutVersionStringBuilder.append(kernelTextView.text)
819 aboutVersionStringBuilder.append("\n")
820 if (webViewProviderTextView.visibility == View.VISIBLE) {
821 aboutVersionStringBuilder.append(webViewProviderTextView.text)
822 aboutVersionStringBuilder.append("\n")
824 aboutVersionStringBuilder.append(webViewVersionTextView.text)
825 aboutVersionStringBuilder.append("\n")
826 if (orbotTextView.visibility == View.VISIBLE) {
827 aboutVersionStringBuilder.append(orbotTextView.text)
828 aboutVersionStringBuilder.append("\n")
830 if (i2pTextView.visibility == View.VISIBLE) {
831 aboutVersionStringBuilder.append(i2pTextView.text)
832 aboutVersionStringBuilder.append("\n")
834 if (openKeychainTextView.visibility == View.VISIBLE) {
835 aboutVersionStringBuilder.append(openKeychainTextView.text)
836 aboutVersionStringBuilder.append("\n")
838 aboutVersionStringBuilder.append("\n")
839 aboutVersionStringBuilder.append(memoryUsageTextView.text)
840 aboutVersionStringBuilder.append("\n")
841 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
842 aboutVersionStringBuilder.append("\n")
843 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
844 aboutVersionStringBuilder.append("\n")
845 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
846 aboutVersionStringBuilder.append("\n")
847 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
848 aboutVersionStringBuilder.append("\n")
849 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
850 aboutVersionStringBuilder.append("\n")
851 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
852 aboutVersionStringBuilder.append("\n")
853 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
854 aboutVersionStringBuilder.append("\n\n")
855 aboutVersionStringBuilder.append(blocklistsTextView.text)
856 aboutVersionStringBuilder.append("\n")
857 aboutVersionStringBuilder.append(easyListTextView.text)
858 aboutVersionStringBuilder.append("\n")
859 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
860 aboutVersionStringBuilder.append("\n")
861 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
862 aboutVersionStringBuilder.append("\n")
863 aboutVersionStringBuilder.append(fanboySocialTextView.text)
864 aboutVersionStringBuilder.append("\n")
865 aboutVersionStringBuilder.append(ultraListTextView.text)
866 aboutVersionStringBuilder.append("\n")
867 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
868 aboutVersionStringBuilder.append("\n\n")
869 aboutVersionStringBuilder.append(packageSignatureTextView.text)
870 aboutVersionStringBuilder.append("\n")
871 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
872 aboutVersionStringBuilder.append("\n")
873 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
874 aboutVersionStringBuilder.append("\n")
875 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
876 aboutVersionStringBuilder.append("\n")
877 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
878 aboutVersionStringBuilder.append("\n")
879 aboutVersionStringBuilder.append(certificateVersionTextView.text)
880 aboutVersionStringBuilder.append("\n")
881 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
882 aboutVersionStringBuilder.append("\n")
883 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
885 // Return the string.
886 return aboutVersionStringBuilder.toString()