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