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 webViewProviderTextView: TextView
109 private lateinit var webViewVersionTextView: TextView
110 private lateinit var orbotTextView: TextView
111 private lateinit var i2pTextView: TextView
112 private lateinit var openKeychainTextView: TextView
113 private lateinit var memoryUsageTextView: TextView
114 private lateinit var appConsumedMemoryTextView: TextView
115 private lateinit var appAvailableMemoryTextView: TextView
116 private lateinit var appTotalMemoryTextView: TextView
117 private lateinit var appMaximumMemoryTextView: TextView
118 private lateinit var systemConsumedMemoryTextView: TextView
119 private lateinit var systemAvailableMemoryTextView: TextView
120 private lateinit var systemTotalMemoryTextView: TextView
121 private lateinit var blocklistsTextView: TextView
122 private lateinit var easyListTextView: TextView
123 private lateinit var easyPrivacyTextView: TextView
124 private lateinit var fanboyAnnoyanceTextView: TextView
125 private lateinit var fanboySocialTextView: TextView
126 private lateinit var ultraListTextView: TextView
127 private lateinit var ultraPrivacyTextView: TextView
128 private lateinit var packageSignatureTextView: TextView
129 private lateinit var certificateIssuerDnTextView: TextView
130 private lateinit var certificateSubjectDnTextView: TextView
131 private lateinit var certificateStartDateTextView: TextView
132 private lateinit var certificateEndDateTextView: TextView
133 private lateinit var certificateVersionTextView: TextView
134 private lateinit var certificateSerialNumberTextView: TextView
135 private lateinit var certificateSignatureAlgorithmTextView: TextView
138 fun createTab(blocklistVersions: Array<String>): AboutVersionFragment {
139 // Create an arguments bundle.
140 val argumentsBundle = Bundle()
142 // Store the arguments in the bundle.
143 argumentsBundle.putStringArray(BLOCKLIST_VERSIONS, blocklistVersions)
145 // Create a new instance of the tab fragment.
146 val aboutVersionFragment = AboutVersionFragment()
148 // Add the arguments bundle to the fragment.
149 aboutVersionFragment.arguments = argumentsBundle
151 // Return the new fragment.
152 return aboutVersionFragment
156 // Define the save about version text activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
157 private val saveAboutVersionTextActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) { fileUri: Uri? ->
158 // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
159 if (fileUri != null) {
161 // Get the about version string.
162 val aboutVersionString = getAboutVersionString()
164 // Open an output stream.
165 val outputStream = requireActivity().contentResolver.openOutputStream(fileUri)!!
167 // Write the about version string to the output stream.
168 outputStream.write(aboutVersionString.toByteArray(StandardCharsets.UTF_8))
170 // Close the output stream.
173 // Initialize the file name string from the file URI last path segment.
174 var fileNameString = fileUri.lastPathSegment
176 // Query the exact file name if the API >= 26.
177 if (Build.VERSION.SDK_INT >= 26) {
178 // Get a cursor from the content resolver.
179 val contentResolverCursor = requireActivity().contentResolver.query(fileUri, null, null, null)!!
181 // Move to the first row.
182 contentResolverCursor.moveToFirst()
184 // Get the file name from the cursor.
185 fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
188 contentResolverCursor.close()
191 // Display a snackbar with the saved logcat information.
192 Snackbar.make(aboutVersionLayout, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show()
193 } catch (exception: Exception) {
194 // Display a snackbar with the error message.
195 Snackbar.make(aboutVersionLayout, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show()
200 // Define the save about version image activity result launcher. It must be defined before `onCreate()` is run or the app will crash.
201 private val saveAboutVersionImageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) { fileUri: Uri? ->
202 // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
203 if (fileUri != null) {
204 // Save the about version image.
205 SaveAboutVersionImage(requireActivity(), fileUri, aboutVersionLayout.findViewById(R.id.about_version_linearlayout)).execute()
209 override fun onCreate(savedInstanceState: Bundle?) {
210 // Run the default commands.
211 super.onCreate(savedInstanceState)
213 // Store the arguments in class variables.
214 blocklistVersions = requireArguments().getStringArray(BLOCKLIST_VERSIONS)!!
216 // Enable the options menu for this fragment.
217 setHasOptionsMenu(true)
220 override fun onCreateView(layoutInflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
221 // 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.
222 aboutVersionLayout = layoutInflater.inflate(R.layout.about_version_scrollview, container, false)
224 // Get handles for the views.
225 privacyBrowserTextView = aboutVersionLayout.findViewById(R.id.privacy_browser_textview)
226 versionTextView = aboutVersionLayout.findViewById(R.id.version)
227 hardwareTextView = aboutVersionLayout.findViewById(R.id.hardware)
228 brandTextView = aboutVersionLayout.findViewById(R.id.brand)
229 manufacturerTextView = aboutVersionLayout.findViewById(R.id.manufacturer)
230 modelTextView = aboutVersionLayout.findViewById(R.id.model)
231 deviceTextView = aboutVersionLayout.findViewById(R.id.device)
232 bootloaderTextView = aboutVersionLayout.findViewById(R.id.bootloader)
233 radioTextView = aboutVersionLayout.findViewById(R.id.radio)
234 softwareTextView = aboutVersionLayout.findViewById(R.id.software)
235 androidTextView = aboutVersionLayout.findViewById(R.id.android)
236 securityPatchTextView = aboutVersionLayout.findViewById(R.id.security_patch)
237 buildTextView = aboutVersionLayout.findViewById(R.id.build)
238 webViewProviderTextView = aboutVersionLayout.findViewById(R.id.webview_provider)
239 webViewVersionTextView = aboutVersionLayout.findViewById(R.id.webview_version)
240 orbotTextView = aboutVersionLayout.findViewById(R.id.orbot)
241 i2pTextView = aboutVersionLayout.findViewById(R.id.i2p)
242 openKeychainTextView = aboutVersionLayout.findViewById(R.id.open_keychain)
243 memoryUsageTextView = aboutVersionLayout.findViewById(R.id.memory_usage)
244 appConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.app_consumed_memory)
245 appAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.app_available_memory)
246 appTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.app_total_memory)
247 appMaximumMemoryTextView = aboutVersionLayout.findViewById(R.id.app_maximum_memory)
248 systemConsumedMemoryTextView = aboutVersionLayout.findViewById(R.id.system_consumed_memory)
249 systemAvailableMemoryTextView = aboutVersionLayout.findViewById(R.id.system_available_memory)
250 systemTotalMemoryTextView = aboutVersionLayout.findViewById(R.id.system_total_memory)
251 blocklistsTextView = aboutVersionLayout.findViewById(R.id.blocklists)
252 easyListTextView = aboutVersionLayout.findViewById(R.id.easylist)
253 easyPrivacyTextView = aboutVersionLayout.findViewById(R.id.easyprivacy)
254 fanboyAnnoyanceTextView = aboutVersionLayout.findViewById(R.id.fanboy_annoyance)
255 fanboySocialTextView = aboutVersionLayout.findViewById(R.id.fanboy_social)
256 ultraListTextView = aboutVersionLayout.findViewById(R.id.ultralist)
257 ultraPrivacyTextView = aboutVersionLayout.findViewById(R.id.ultraprivacy)
258 packageSignatureTextView = aboutVersionLayout.findViewById(R.id.package_signature)
259 certificateIssuerDnTextView = aboutVersionLayout.findViewById(R.id.certificate_issuer_dn)
260 certificateSubjectDnTextView = aboutVersionLayout.findViewById(R.id.certificate_subject_dn)
261 certificateStartDateTextView = aboutVersionLayout.findViewById(R.id.certificate_start_date)
262 certificateEndDateTextView = aboutVersionLayout.findViewById(R.id.certificate_end_date)
263 certificateVersionTextView = aboutVersionLayout.findViewById(R.id.certificate_version)
264 certificateSerialNumberTextView = aboutVersionLayout.findViewById(R.id.certificate_serial_number)
265 certificateSignatureAlgorithmTextView = aboutVersionLayout.findViewById(R.id.certificate_signature_algorithm)
268 val version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + BuildConfig.VERSION_CODE + ")"
269 val brandLabel = getString(R.string.brand) + " "
270 val manufacturerLabel = getString(R.string.manufacturer) + " "
271 val modelLabel = getString(R.string.model) + " "
272 val deviceLabel = getString(R.string.device) + " "
273 val bootloaderLabel = getString(R.string.bootloader) + " "
274 val androidLabel = getString(R.string.android) + " "
275 val buildLabel = getString(R.string.build) + " "
276 val webViewVersionLabel = getString(R.string.webview_version) + " "
277 appConsumedMemoryLabel = getString(R.string.app_consumed_memory) + " "
278 appAvailableMemoryLabel = getString(R.string.app_available_memory) + " "
279 appTotalMemoryLabel = getString(R.string.app_total_memory) + " "
280 appMaximumMemoryLabel = getString(R.string.app_maximum_memory) + " "
281 systemConsumedMemoryLabel = getString(R.string.system_consumed_memory) + " "
282 systemAvailableMemoryLabel = getString(R.string.system_available_memory) + " "
283 systemTotalMemoryLabel = getString(R.string.system_total_memory) + " "
284 val easyListLabel = getString(R.string.easylist_label) + " "
285 val easyPrivacyLabel = getString(R.string.easyprivacy_label) + " "
286 val fanboyAnnoyanceLabel = getString(R.string.fanboy_annoyance_label) + " "
287 val fanboySocialLabel = getString(R.string.fanboy_social_label) + " "
288 val ultraListLabel = getString(R.string.ultralist_label) + " "
289 val ultraPrivacyLabel = getString(R.string.ultraprivacy_label) + " "
290 val issuerDNLabel = getString(R.string.issuer_dn) + " "
291 val subjectDNLabel = getString(R.string.subject_dn) + " "
292 val startDateLabel = getString(R.string.start_date) + " "
293 val endDateLabel = getString(R.string.end_date) + " "
294 val certificateVersionLabel = getString(R.string.certificate_version) + " "
295 val serialNumberLabel = getString(R.string.serial_number) + " "
296 val signatureAlgorithmLabel = getString(R.string.signature_algorithm) + " "
298 // 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.
299 // Once the minimum API >= 26 this can be accomplished with the WebView package info.
300 val webViewLayout = layoutInflater.inflate(R.layout.bare_webview, container, false)
301 val tabLayoutWebView = webViewLayout.findViewById<WebView>(R.id.bare_webview)
302 val userAgentString = tabLayoutWebView.settings.userAgentString
304 // Get the device's information and store it in strings.
305 val brand = Build.BRAND
306 val manufacturer = Build.MANUFACTURER
307 val model = Build.MODEL
308 val device = Build.DEVICE
309 val bootloader = Build.BOOTLOADER
310 val radio = Build.getRadioVersion()
311 val android = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Build.VERSION.SDK_INT + ")"
312 val build = Build.DISPLAY
313 // Select the substring that begins after `Chrome/` and goes until the next ` `.
314 val webView = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/")))
316 // Get the Orbot version name if Orbot is installed.
317 val orbot: String = try {
318 // Store the version name.
319 requireContext().packageManager.getPackageInfo("org.torproject.android", 0).versionName
320 } catch (exception: PackageManager.NameNotFoundException) { // Orbot is not installed.
321 // Store an empty string.
325 // Get the I2P version name if I2P is installed.
326 val i2p: String = try {
327 // Store the version name.
328 requireContext().packageManager.getPackageInfo("net.i2p.android.router", 0).versionName
329 } catch (exception: PackageManager.NameNotFoundException) { // I2P is not installed.
330 // Store an empty string.
334 // Get the OpenKeychain version name if it is installed.
335 val openKeychain: String = try {
336 // Store the version name.
337 requireContext().packageManager.getPackageInfo("org.sufficientlysecure.keychain", 0).versionName
338 } catch (exception: PackageManager.NameNotFoundException) { // OpenKeychain is not installed.
339 // Store an empty string.
343 // Create a spannable string builder for the hardware and software text views that need multiple colors of text.
344 val brandStringBuilder = SpannableStringBuilder(brandLabel + brand)
345 val manufacturerStringBuilder = SpannableStringBuilder(manufacturerLabel + manufacturer)
346 val modelStringBuilder = SpannableStringBuilder(modelLabel + model)
347 val deviceStringBuilder = SpannableStringBuilder(deviceLabel + device)
348 val bootloaderStringBuilder = SpannableStringBuilder(bootloaderLabel + bootloader)
349 val androidStringBuilder = SpannableStringBuilder(androidLabel + android)
350 val buildStringBuilder = SpannableStringBuilder(buildLabel + build)
351 val webViewVersionStringBuilder = SpannableStringBuilder(webViewVersionLabel + webView)
352 val easyListStringBuilder = SpannableStringBuilder(easyListLabel + blocklistVersions[0])
353 val easyPrivacyStringBuilder = SpannableStringBuilder(easyPrivacyLabel + blocklistVersions[1])
354 val fanboyAnnoyanceStringBuilder = SpannableStringBuilder(fanboyAnnoyanceLabel + blocklistVersions[2])
355 val fanboySocialStringBuilder = SpannableStringBuilder(fanboySocialLabel + blocklistVersions[3])
356 val ultraListStringBuilder = SpannableStringBuilder(ultraListLabel + blocklistVersions[4])
357 val ultraPrivacyStringBuilder = SpannableStringBuilder(ultraPrivacyLabel + blocklistVersions[5])
359 // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23.
360 blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.about_version_blue_text))
362 // Set the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
363 brandStringBuilder.setSpan(blueColorSpan, brandLabel.length, brandStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
364 manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length, manufacturerStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
365 modelStringBuilder.setSpan(blueColorSpan, modelLabel.length, modelStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
366 deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length, deviceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
367 bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length, bootloaderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
368 androidStringBuilder.setSpan(blueColorSpan, androidLabel.length, androidStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
369 buildStringBuilder.setSpan(blueColorSpan, buildLabel.length, buildStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
370 webViewVersionStringBuilder.setSpan(blueColorSpan, webViewVersionLabel.length, webViewVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
371 easyListStringBuilder.setSpan(blueColorSpan, easyListLabel.length, easyListStringBuilder.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
372 easyPrivacyStringBuilder.setSpan(blueColorSpan, easyPrivacyLabel.length, easyPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
373 fanboyAnnoyanceStringBuilder.setSpan(blueColorSpan, fanboyAnnoyanceLabel.length, fanboyAnnoyanceStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
374 fanboySocialStringBuilder.setSpan(blueColorSpan, fanboySocialLabel.length, fanboySocialStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
375 ultraListStringBuilder.setSpan(blueColorSpan, ultraListLabel.length, ultraListStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
376 ultraPrivacyStringBuilder.setSpan(blueColorSpan, ultraPrivacyLabel.length, ultraPrivacyStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
378 // Display the strings in the text boxes.
379 versionTextView.text = version
380 brandTextView.text = brandStringBuilder
381 manufacturerTextView.text = manufacturerStringBuilder
382 modelTextView.text = modelStringBuilder
383 deviceTextView.text = deviceStringBuilder
384 bootloaderTextView.text = bootloaderStringBuilder
385 androidTextView.text = androidStringBuilder
386 buildTextView.text = buildStringBuilder
387 webViewVersionTextView.text = webViewVersionStringBuilder
388 easyListTextView.text = easyListStringBuilder
389 easyPrivacyTextView.text = easyPrivacyStringBuilder
390 fanboyAnnoyanceTextView.text = fanboyAnnoyanceStringBuilder
391 fanboySocialTextView.text = fanboySocialStringBuilder
392 ultraListTextView.text = ultraListStringBuilder
393 ultraPrivacyTextView.text = ultraPrivacyStringBuilder
395 // Only populate the radio text view if there is a radio in the device.
396 // 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>
397 if (radio != null && radio.isNotEmpty()) {
399 val radioLabel = getString(R.string.radio) + " "
401 // Create a spannable string builder.
402 val radioStringBuilder = SpannableStringBuilder(radioLabel + radio)
404 // Set the span to display the radio in blue.
405 radioStringBuilder.setSpan(blueColorSpan, radioLabel.length, radioStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
407 // Display the string in the text view.
408 radioTextView.text = radioStringBuilder
409 } else { // This device does not have a radio.
410 // Hide the radio text view.
411 radioTextView.visibility = View.GONE
415 val securityPatchLabel = getString(R.string.security_patch) + " "
417 // Get the security patch version.
418 val securityPatch = Build.VERSION.SECURITY_PATCH
420 // Create a spannable string builder.
421 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
423 // Set the span to display the security patch version in blue.
424 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
426 // Display the string in the text view.
427 securityPatchTextView.text = securityPatchStringBuilder
429 // Create the WebView provider label.
430 val webViewProviderLabel = getString(R.string.webview_provider) + " "
432 // Get the current WebView package info.
433 val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(requireContext())!!
435 // Get the WebView provider name.
436 val webViewPackageName = webViewPackageInfo.packageName
438 // Create the spannable string builder.
439 val webViewProviderStringBuilder = SpannableStringBuilder(webViewProviderLabel + webViewPackageName)
441 // Apply the coloration.
442 webViewProviderStringBuilder.setSpan(blueColorSpan, webViewProviderLabel.length, webViewProviderStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
444 // Display the WebView provider.
445 webViewProviderTextView.text = webViewProviderStringBuilder
447 // Only populate the Orbot text view if it is installed.
448 if (orbot.isNotEmpty()) {
450 val orbotLabel = getString(R.string.orbot) + " "
452 // Create a spannable string builder.
453 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
455 // Set the span to display the Orbot version.
456 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
458 // Display the string in the text view.
459 orbotTextView.text = orbotStringBuilder
460 } else { // Orbot is not installed.
461 // Hide the Orbot text view.
462 orbotTextView.visibility = View.GONE
465 // Only populate the I2P text view if it is installed.
466 if (i2p.isNotEmpty()) {
468 val i2pLabel = getString(R.string.i2p) + " "
470 // Create a spannable string builder.
471 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
473 // Set the span to display the I2P version.
474 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
476 // Display the string in the text view.
477 i2pTextView.text = i2pStringBuilder
478 } else { // I2P is not installed.
479 // Hide the I2P text view.
480 i2pTextView.visibility = View.GONE
483 // Only populate the OpenKeychain text view if it is installed.
484 if (openKeychain.isNotEmpty()) {
486 val openKeychainLabel = getString(R.string.openkeychain) + " "
488 // Create a spannable string builder.
489 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
491 // Set the span to display the OpenKeychain version.
492 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
494 // Display the string in the text view.
495 openKeychainTextView.text = openKeychainStringBuilder
496 } else { //OpenKeychain is not installed.
497 // Hide the OpenKeychain text view.
498 openKeychainTextView.visibility = View.GONE
501 // Display the package signature.
503 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
504 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead.
505 @SuppressLint("PackageManagerGetSignatures") val packageSignature = requireContext().packageManager.getPackageInfo(requireContext().packageName,PackageManager.GET_SIGNATURES)
508 // Convert the signature to a byte array input stream.
509 val certificateByteArrayInputStream: InputStream = ByteArrayInputStream(packageSignature.toByteArray())
511 // Display the certificate information on the screen.
513 // Instantiate a certificate factory.
514 val certificateFactory = CertificateFactory.getInstance("X509")
516 // Generate an X509 certificate.
517 val x509Certificate = certificateFactory.generateCertificate(certificateByteArrayInputStream) as X509Certificate
519 // Store the individual sections of the certificate.
520 val issuerDNPrincipal = x509Certificate.issuerDN
521 val subjectDNPrincipal = x509Certificate.subjectDN
522 val startDate = x509Certificate.notBefore
523 val endDate = x509Certificate.notAfter
524 val certificateVersion = x509Certificate.version
525 val serialNumberBigInteger = x509Certificate.serialNumber
526 val signatureAlgorithmNameString = x509Certificate.sigAlgName
528 // Create a spannable string builder for each text view that needs multiple colors of text.
529 val issuerDNStringBuilder = SpannableStringBuilder(issuerDNLabel + issuerDNPrincipal.toString())
530 val subjectDNStringBuilder = SpannableStringBuilder(subjectDNLabel + subjectDNPrincipal.toString())
531 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
532 val endDataStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
533 val certificateVersionStringBuilder = SpannableStringBuilder(certificateVersionLabel + certificateVersion)
534 val serialNumberStringBuilder = SpannableStringBuilder(serialNumberLabel + serialNumberBigInteger)
535 val signatureAlgorithmStringBuilder = SpannableStringBuilder(signatureAlgorithmLabel + signatureAlgorithmNameString)
537 // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
538 issuerDNStringBuilder.setSpan(blueColorSpan, issuerDNLabel.length, issuerDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
539 subjectDNStringBuilder.setSpan(blueColorSpan, subjectDNLabel.length, subjectDNStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
540 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
541 endDataStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDataStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
542 certificateVersionStringBuilder.setSpan(blueColorSpan, certificateVersionLabel.length, certificateVersionStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
543 serialNumberStringBuilder.setSpan(blueColorSpan, serialNumberLabel.length, serialNumberStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
544 signatureAlgorithmStringBuilder.setSpan(blueColorSpan, signatureAlgorithmLabel.length, signatureAlgorithmStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
546 // Display the strings in the text boxes.
547 certificateIssuerDnTextView.text = issuerDNStringBuilder
548 certificateSubjectDnTextView.text = subjectDNStringBuilder
549 certificateStartDateTextView.text = startDateStringBuilder
550 certificateEndDateTextView.text = endDataStringBuilder
551 certificateVersionTextView.text = certificateVersionStringBuilder
552 certificateSerialNumberTextView.text = serialNumberStringBuilder
553 certificateSignatureAlgorithmTextView.text = signatureAlgorithmStringBuilder
554 } catch (certificateException: CertificateException) {
555 // Do nothing if there is a certificate error.
558 // Get a handle for the runtime.
559 runtime = Runtime.getRuntime()
561 // Get a handle for the activity manager.
562 activityManager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
564 // Instantiate a memory info variable.
565 memoryInfo = ActivityManager.MemoryInfo()
567 // Define a number format.
568 numberFormat = NumberFormat.getInstance()
570 // Set the minimum and maximum number of fraction digits.
571 numberFormat.minimumFractionDigits = 2
572 numberFormat.maximumFractionDigits = 2
574 // Update the memory usage.
575 updateMemoryUsage(requireActivity())
576 } catch (e: PackageManager.NameNotFoundException) {
577 // Do nothing if the package manager says Privacy Browser isn't installed.
580 // Scroll the tab if the saved instance state is not null.
581 if (savedInstanceState != null) {
582 aboutVersionLayout.post {
583 aboutVersionLayout.scrollX = savedInstanceState.getInt("scroll_x")
584 aboutVersionLayout.scrollY = savedInstanceState.getInt("scroll_y")
588 // Return the tab layout.
589 return aboutVersionLayout
592 override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
593 // Inflate the about version menu.
594 menuInflater.inflate(R.menu.about_version_options_menu, menu)
596 // Run the default commands.
597 super.onCreateOptionsMenu(menu, menuInflater)
600 override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
601 // Run the appropriate commands.
602 when (menuItem.itemId) {
603 R.id.copy -> { // Copy.
604 // Get the about version string.
605 val aboutVersionString = getAboutVersionString()
607 // Get a handle for the clipboard manager.
608 val clipboardManager = (requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
610 // Save the about version string in a clip data.
611 val aboutVersionClipData = ClipData.newPlainText(getString(R.string.about), aboutVersionString)
613 // Place the clip data on the clipboard.
614 clipboardManager.setPrimaryClip(aboutVersionClipData)
616 // Display a snackbar.
617 Snackbar.make(aboutVersionLayout, R.string.version_info_copied, Snackbar.LENGTH_SHORT).show()
619 // Consume the event.
623 R.id.share -> { // Share.
624 // Get the about version string.
625 val aboutString = getAboutVersionString()
627 // Create an email intent.
628 val emailIntent = Intent(Intent.ACTION_SEND)
630 // Add the about version string to the intent.
631 emailIntent.putExtra(Intent.EXTRA_TEXT, aboutString)
633 // Set the MIME type.
634 emailIntent.type = "text/plain"
636 // Set the intent to open in a new task.
637 emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
640 startActivity(Intent.createChooser(emailIntent, getString(R.string.share)))
642 // Consume the event.
646 R.id.save_text -> { // Save text.
647 // Open the file picker.
648 saveAboutVersionTextActivityResultLauncher.launch(getString(R.string.privacy_browser_version_txt, BuildConfig.VERSION_NAME))
650 // Consume the event.
654 R.id.save_image -> { // Save image.
655 // Open the file picker.
656 saveAboutVersionImageActivityResultLauncher.launch(getString(R.string.privacy_browser_version_png, BuildConfig.VERSION_NAME))
658 // Consume the event.
661 else -> { // The home button was selected.
662 // Run the parents class on return.
663 return super.onOptionsItemSelected(menuItem)
668 override fun onSaveInstanceState(savedInstanceState: Bundle) {
669 // Run the default commands.
670 super.onSaveInstanceState(savedInstanceState)
672 // Save the scroll positions.
673 savedInstanceState.putInt("scroll_x", aboutVersionLayout.scrollX)
674 savedInstanceState.putInt("scroll_y", aboutVersionLayout.scrollY)
677 override fun onPause() {
678 // Run the default commands.
681 // Pause the updating of the memory usage.
682 updateMemoryUsageBoolean = false
685 override fun onResume() {
686 // Run the default commands.
689 // Resume the updating of the memory usage.
690 updateMemoryUsageBoolean = true
693 fun updateMemoryUsage(activity: Activity) {
695 // Update the memory usage if enabled.
696 if (updateMemoryUsageBoolean) {
697 // Populate the memory info variable.
698 activityManager.getMemoryInfo(memoryInfo)
700 // Get the app memory information.
701 val appAvailableMemoryLong = runtime.freeMemory()
702 val appTotalMemoryLong = runtime.totalMemory()
703 val appMaximumMemoryLong = runtime.maxMemory()
705 // Calculate the app consumed memory.
706 val appConsumedMemoryLong = appTotalMemoryLong - appAvailableMemoryLong
708 // Get the system memory information.
709 val systemTotalMemoryLong = memoryInfo.totalMem
710 val systemAvailableMemoryLong = memoryInfo.availMem
712 // Calculate the system consumed memory.
713 val systemConsumedMemoryLong = systemTotalMemoryLong - systemAvailableMemoryLong
715 // Convert the memory information into mebibytes.
716 val appConsumedMemoryFloat = appConsumedMemoryLong.toFloat() / MEBIBYTE
717 val appAvailableMemoryFloat = appAvailableMemoryLong.toFloat() / MEBIBYTE
718 val appTotalMemoryFloat = appTotalMemoryLong.toFloat() / MEBIBYTE
719 val appMaximumMemoryFloat = appMaximumMemoryLong.toFloat() / MEBIBYTE
720 val systemConsumedMemoryFloat = systemConsumedMemoryLong.toFloat() / MEBIBYTE
721 val systemAvailableMemoryFloat = systemAvailableMemoryLong.toFloat() / MEBIBYTE
722 val systemTotalMemoryFloat = systemTotalMemoryLong.toFloat() / MEBIBYTE
724 // Get the mebibyte string.
725 val mebibyte = getString(R.string.mebibyte)
727 // Calculate the mebibyte length.
728 val mebibyteLength = mebibyte.length
730 // Create spannable string builders.
731 val appConsumedMemoryStringBuilder = SpannableStringBuilder(appConsumedMemoryLabel + numberFormat.format(appConsumedMemoryFloat.toDouble()) + " " + mebibyte)
732 val appAvailableMemoryStringBuilder = SpannableStringBuilder(appAvailableMemoryLabel + numberFormat.format(appAvailableMemoryFloat.toDouble()) + " " + mebibyte)
733 val appTotalMemoryStringBuilder = SpannableStringBuilder(appTotalMemoryLabel + numberFormat.format(appTotalMemoryFloat.toDouble()) + " " + mebibyte)
734 val appMaximumMemoryStringBuilder = SpannableStringBuilder(appMaximumMemoryLabel + numberFormat.format(appMaximumMemoryFloat.toDouble()) + " " + mebibyte)
735 val systemConsumedMemoryStringBuilder = SpannableStringBuilder(systemConsumedMemoryLabel + numberFormat.format(systemConsumedMemoryFloat.toDouble()) + " " + mebibyte)
736 val systemAvailableMemoryStringBuilder = SpannableStringBuilder(systemAvailableMemoryLabel + numberFormat.format(systemAvailableMemoryFloat.toDouble()) + " " + mebibyte)
737 val systemTotalMemoryStringBuilder = SpannableStringBuilder(systemTotalMemoryLabel + numberFormat.format(systemTotalMemoryFloat.toDouble()) + " " + mebibyte)
739 // Setup the spans to display the memory information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
740 appConsumedMemoryStringBuilder.setSpan(blueColorSpan, appConsumedMemoryLabel.length, appConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
741 appAvailableMemoryStringBuilder.setSpan(blueColorSpan, appAvailableMemoryLabel.length, appAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
742 appTotalMemoryStringBuilder.setSpan(blueColorSpan, appTotalMemoryLabel.length, appTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
743 appMaximumMemoryStringBuilder.setSpan(blueColorSpan, appMaximumMemoryLabel.length, appMaximumMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
744 systemConsumedMemoryStringBuilder.setSpan(blueColorSpan, systemConsumedMemoryLabel.length, systemConsumedMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
745 systemAvailableMemoryStringBuilder.setSpan(blueColorSpan, systemAvailableMemoryLabel.length, systemAvailableMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
746 systemTotalMemoryStringBuilder.setSpan(blueColorSpan, systemTotalMemoryLabel.length, systemTotalMemoryStringBuilder.length - mebibyteLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
748 // Display the string in the text boxes.
749 appConsumedMemoryTextView.text = appConsumedMemoryStringBuilder
750 appAvailableMemoryTextView.text = appAvailableMemoryStringBuilder
751 appTotalMemoryTextView.text = appTotalMemoryStringBuilder
752 appMaximumMemoryTextView.text = appMaximumMemoryStringBuilder
753 systemConsumedMemoryTextView.text = systemConsumedMemoryStringBuilder
754 systemAvailableMemoryTextView.text = systemAvailableMemoryStringBuilder
755 systemTotalMemoryTextView.text = systemTotalMemoryStringBuilder
758 // Schedule another memory update if the activity has not been destroyed.
759 if (!activity.isDestroyed) {
760 // Create a handler to update the memory usage.
761 val updateMemoryUsageHandler = Handler(Looper.getMainLooper())
763 // Create a runnable to update the memory usage.
764 val updateMemoryUsageRunnable = Runnable { updateMemoryUsage(activity) }
766 // Update the memory usage after 1000 milliseconds
767 updateMemoryUsageHandler.postDelayed(updateMemoryUsageRunnable, 1000)
769 } catch (exception: Exception) {
774 fun getAboutVersionString(): String {
775 // Initialize an about version string builder.
776 val aboutVersionStringBuilder = StringBuilder()
778 // Populate the about version string builder.
779 aboutVersionStringBuilder.append(privacyBrowserTextView.text)
780 aboutVersionStringBuilder.append("\n")
781 aboutVersionStringBuilder.append(versionTextView.text)
782 aboutVersionStringBuilder.append("\n\n")
783 aboutVersionStringBuilder.append(hardwareTextView.text)
784 aboutVersionStringBuilder.append("\n")
785 aboutVersionStringBuilder.append(brandTextView.text)
786 aboutVersionStringBuilder.append("\n")
787 aboutVersionStringBuilder.append(manufacturerTextView.text)
788 aboutVersionStringBuilder.append("\n")
789 aboutVersionStringBuilder.append(modelTextView.text)
790 aboutVersionStringBuilder.append("\n")
791 aboutVersionStringBuilder.append(deviceTextView.text)
792 aboutVersionStringBuilder.append("\n")
793 aboutVersionStringBuilder.append(bootloaderTextView.text)
794 aboutVersionStringBuilder.append("\n")
795 if (radioTextView.visibility == View.VISIBLE) {
796 aboutVersionStringBuilder.append(radioTextView.text)
797 aboutVersionStringBuilder.append("\n")
799 aboutVersionStringBuilder.append("\n")
800 aboutVersionStringBuilder.append(softwareTextView.text)
801 aboutVersionStringBuilder.append("\n")
802 aboutVersionStringBuilder.append(androidTextView.text)
803 aboutVersionStringBuilder.append("\n")
804 if (securityPatchTextView.visibility == View.VISIBLE) {
805 aboutVersionStringBuilder.append(securityPatchTextView.text)
806 aboutVersionStringBuilder.append("\n")
808 aboutVersionStringBuilder.append(buildTextView.text)
809 aboutVersionStringBuilder.append("\n")
810 if (webViewProviderTextView.visibility == View.VISIBLE) {
811 aboutVersionStringBuilder.append(webViewProviderTextView.text)
812 aboutVersionStringBuilder.append("\n")
814 aboutVersionStringBuilder.append(webViewVersionTextView.text)
815 aboutVersionStringBuilder.append("\n")
816 if (orbotTextView.visibility == View.VISIBLE) {
817 aboutVersionStringBuilder.append(orbotTextView.text)
818 aboutVersionStringBuilder.append("\n")
820 if (i2pTextView.visibility == View.VISIBLE) {
821 aboutVersionStringBuilder.append(i2pTextView.text)
822 aboutVersionStringBuilder.append("\n")
824 if (openKeychainTextView.visibility == View.VISIBLE) {
825 aboutVersionStringBuilder.append(openKeychainTextView.text)
826 aboutVersionStringBuilder.append("\n")
828 aboutVersionStringBuilder.append("\n")
829 aboutVersionStringBuilder.append(memoryUsageTextView.text)
830 aboutVersionStringBuilder.append("\n")
831 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
832 aboutVersionStringBuilder.append("\n")
833 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
834 aboutVersionStringBuilder.append("\n")
835 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
836 aboutVersionStringBuilder.append("\n")
837 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
838 aboutVersionStringBuilder.append("\n")
839 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
840 aboutVersionStringBuilder.append("\n")
841 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
842 aboutVersionStringBuilder.append("\n")
843 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
844 aboutVersionStringBuilder.append("\n\n")
845 aboutVersionStringBuilder.append(blocklistsTextView.text)
846 aboutVersionStringBuilder.append("\n")
847 aboutVersionStringBuilder.append(easyListTextView.text)
848 aboutVersionStringBuilder.append("\n")
849 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
850 aboutVersionStringBuilder.append("\n")
851 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
852 aboutVersionStringBuilder.append("\n")
853 aboutVersionStringBuilder.append(fanboySocialTextView.text)
854 aboutVersionStringBuilder.append("\n")
855 aboutVersionStringBuilder.append(ultraListTextView.text)
856 aboutVersionStringBuilder.append("\n")
857 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
858 aboutVersionStringBuilder.append("\n\n")
859 aboutVersionStringBuilder.append(packageSignatureTextView.text)
860 aboutVersionStringBuilder.append("\n")
861 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
862 aboutVersionStringBuilder.append("\n")
863 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
864 aboutVersionStringBuilder.append("\n")
865 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
866 aboutVersionStringBuilder.append("\n")
867 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
868 aboutVersionStringBuilder.append("\n")
869 aboutVersionStringBuilder.append(certificateVersionTextView.text)
870 aboutVersionStringBuilder.append("\n")
871 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
872 aboutVersionStringBuilder.append("\n")
873 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
875 // Return the string.
876 return aboutVersionStringBuilder.toString()