2 * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser 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 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. 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.getColumnIndex(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(resources.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
414 // Build.VERSION.SECURITY_PATCH is only available for SDK_INT >= 23.
415 if (Build.VERSION.SDK_INT >= 23) {
417 val securityPatchLabel = getString(R.string.security_patch) + " "
419 // Get the security patch version.
420 val securityPatch = Build.VERSION.SECURITY_PATCH
422 // Create a spannable string builder.
423 val securityPatchStringBuilder = SpannableStringBuilder(securityPatchLabel + securityPatch)
425 // Set the span to display the security patch version in blue.
426 securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length, securityPatchStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
428 // Display the string in the text view.
429 securityPatchTextView.text = securityPatchStringBuilder
430 } else { // The API < 23.
431 // Hide the security patch text view.
432 securityPatchTextView.visibility = View.GONE
435 // Only populate the WebView provider if the SDK >= 21.
436 if (Build.VERSION.SDK_INT >= 21) {
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
454 } else { // The API < 21.
455 // Hide the WebView provider text view.
456 webViewProviderTextView.visibility = View.GONE
459 // Only populate the Orbot text view if it is installed.
460 if (orbot.isNotEmpty()) {
462 val orbotLabel = getString(R.string.orbot) + " "
464 // Create a spannable string builder.
465 val orbotStringBuilder = SpannableStringBuilder(orbotLabel + orbot)
467 // Set the span to display the Orbot version.
468 orbotStringBuilder.setSpan(blueColorSpan, orbotLabel.length, orbotStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
470 // Display the string in the text view.
471 orbotTextView.text = orbotStringBuilder
472 } else { // Orbot is not installed.
473 // Hide the Orbot text view.
474 orbotTextView.visibility = View.GONE
477 // Only populate the I2P text view if it is installed.
478 if (i2p.isNotEmpty()) {
480 val i2pLabel = getString(R.string.i2p) + " "
482 // Create a spannable string builder.
483 val i2pStringBuilder = SpannableStringBuilder(i2pLabel + i2p)
485 // Set the span to display the I2P version.
486 i2pStringBuilder.setSpan(blueColorSpan, i2pLabel.length, i2pStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
488 // Display the string in the text view.
489 i2pTextView.text = i2pStringBuilder
490 } else { // I2P is not installed.
491 // Hide the I2P text view.
492 i2pTextView.visibility = View.GONE
495 // Only populate the OpenKeychain text view if it is installed.
496 if (openKeychain.isNotEmpty()) {
498 val openKeychainLabel = getString(R.string.openkeychain) + " "
500 // Create a spannable string builder.
501 val openKeychainStringBuilder = SpannableStringBuilder(openKeychainLabel + openKeychain)
503 // Set the span to display the OpenKeychain version.
504 openKeychainStringBuilder.setSpan(blueColorSpan, openKeychainLabel.length, openKeychainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
506 // Display the string in the text view.
507 openKeychainTextView.text = openKeychainStringBuilder
508 } else { //OpenKeychain is not installed.
509 // Hide the OpenKeychain text view.
510 openKeychainTextView.visibility = View.GONE
513 // Display the package signature.
515 // Get the first package signature. Suppress the lint warning about the need to be careful in implementing comparison of certificates for security purposes.
516 // Once the minimum API >= 28, `GET_SIGNING_CERTIFICATES` can be used instead.
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 if (webViewProviderTextView.visibility == View.VISIBLE) {
823 aboutVersionStringBuilder.append(webViewProviderTextView.text)
824 aboutVersionStringBuilder.append("\n")
826 aboutVersionStringBuilder.append(webViewVersionTextView.text)
827 aboutVersionStringBuilder.append("\n")
828 if (orbotTextView.visibility == View.VISIBLE) {
829 aboutVersionStringBuilder.append(orbotTextView.text)
830 aboutVersionStringBuilder.append("\n")
832 if (i2pTextView.visibility == View.VISIBLE) {
833 aboutVersionStringBuilder.append(i2pTextView.text)
834 aboutVersionStringBuilder.append("\n")
836 if (openKeychainTextView.visibility == View.VISIBLE) {
837 aboutVersionStringBuilder.append(openKeychainTextView.text)
838 aboutVersionStringBuilder.append("\n")
840 aboutVersionStringBuilder.append("\n")
841 aboutVersionStringBuilder.append(memoryUsageTextView.text)
842 aboutVersionStringBuilder.append("\n")
843 aboutVersionStringBuilder.append(appConsumedMemoryTextView.text)
844 aboutVersionStringBuilder.append("\n")
845 aboutVersionStringBuilder.append(appAvailableMemoryTextView.text)
846 aboutVersionStringBuilder.append("\n")
847 aboutVersionStringBuilder.append(appTotalMemoryTextView.text)
848 aboutVersionStringBuilder.append("\n")
849 aboutVersionStringBuilder.append(appMaximumMemoryTextView.text)
850 aboutVersionStringBuilder.append("\n")
851 aboutVersionStringBuilder.append(systemConsumedMemoryTextView.text)
852 aboutVersionStringBuilder.append("\n")
853 aboutVersionStringBuilder.append(systemAvailableMemoryTextView.text)
854 aboutVersionStringBuilder.append("\n")
855 aboutVersionStringBuilder.append(systemTotalMemoryTextView.text)
856 aboutVersionStringBuilder.append("\n\n")
857 aboutVersionStringBuilder.append(blocklistsTextView.text)
858 aboutVersionStringBuilder.append("\n")
859 aboutVersionStringBuilder.append(easyListTextView.text)
860 aboutVersionStringBuilder.append("\n")
861 aboutVersionStringBuilder.append(easyPrivacyTextView.text)
862 aboutVersionStringBuilder.append("\n")
863 aboutVersionStringBuilder.append(fanboyAnnoyanceTextView.text)
864 aboutVersionStringBuilder.append("\n")
865 aboutVersionStringBuilder.append(fanboySocialTextView.text)
866 aboutVersionStringBuilder.append("\n")
867 aboutVersionStringBuilder.append(ultraListTextView.text)
868 aboutVersionStringBuilder.append("\n")
869 aboutVersionStringBuilder.append(ultraPrivacyTextView.text)
870 aboutVersionStringBuilder.append("\n\n")
871 aboutVersionStringBuilder.append(packageSignatureTextView.text)
872 aboutVersionStringBuilder.append("\n")
873 aboutVersionStringBuilder.append(certificateIssuerDnTextView.text)
874 aboutVersionStringBuilder.append("\n")
875 aboutVersionStringBuilder.append(certificateSubjectDnTextView.text)
876 aboutVersionStringBuilder.append("\n")
877 aboutVersionStringBuilder.append(certificateStartDateTextView.text)
878 aboutVersionStringBuilder.append("\n")
879 aboutVersionStringBuilder.append(certificateEndDateTextView.text)
880 aboutVersionStringBuilder.append("\n")
881 aboutVersionStringBuilder.append(certificateVersionTextView.text)
882 aboutVersionStringBuilder.append("\n")
883 aboutVersionStringBuilder.append(certificateSerialNumberTextView.text)
884 aboutVersionStringBuilder.append("\n")
885 aboutVersionStringBuilder.append(certificateSignatureAlgorithmTextView.text)
887 // Return the string.
888 return aboutVersionStringBuilder.toString()