X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FLogcatActivity.kt;fp=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FLogcatActivity.kt;h=c12c74ecb4302c403067724b84ddcf35ec77c011;hp=4005443ae1c0d0e637c48dd7639713093b91c1b0;hb=a9cc33fde503c6c8b0a870e0a5d4287e38a4f696;hpb=74e8a8368db991ee511c216a298c4ff5201f41de diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt index 4005443a..c12c74ec 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt @@ -1,7 +1,7 @@ /* * Copyright 2019-2024 Soren Stoutner . * - * This file is part of Privacy Browser Android . + * This file is part of Privacy Browser Android . * * Privacy Browser Android is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,12 +24,12 @@ import android.content.ClipboardManager import android.os.Build import android.os.Bundle import android.provider.OpenableColumns +import android.util.Base64 import android.util.TypedValue import android.view.Menu import android.view.MenuItem import android.view.WindowManager -import android.widget.TextView -import android.widget.ScrollView +import android.webkit.WebView import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity @@ -50,29 +50,25 @@ import kotlinx.coroutines.withContext import java.io.BufferedReader import java.io.IOException import java.io.InputStreamReader -import java.lang.Exception + import java.nio.charset.StandardCharsets // Define the class constants. -private const val SCROLLVIEW_POSITION = "scrollview_position" +private const val SCROLL_Y = "A" class LogcatActivity : AppCompatActivity() { - // Define the class variables. - private var scrollViewYPositionInt = 0 + // Declare the class variables. + private lateinit var logcatPlainTextStringBuilder: StringBuilder - // Define the class views. + // Declare the class views. private lateinit var swipeRefreshLayout: SwipeRefreshLayout - private lateinit var logcatScrollView: ScrollView - private lateinit var logcatTextView: TextView + private lateinit var logcatWebView: WebView // Define the save logcat activity result launcher. It must be defined before `onCreate()` is run or the app will crash. private val saveLogcatActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri -> // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back. if (fileUri != null) { try { - // Get the logcat string. - val logcatString = logcatTextView.text.toString() - // Open an output stream. val outputStream = contentResolver.openOutputStream(fileUri)!! @@ -80,7 +76,7 @@ class LogcatActivity : AppCompatActivity() { CoroutineScope(Dispatchers.Main).launch { withContext(Dispatchers.IO) { // Write the logcat string to the output stream. - outputStream.write(logcatString.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(logcatPlainTextStringBuilder.toString().toByteArray(StandardCharsets.UTF_8)) // Close the output stream. outputStream.close() @@ -100,10 +96,10 @@ class LogcatActivity : AppCompatActivity() { contentResolverCursor.close() // Display a snackbar with the saved logcat information. - Snackbar.make(logcatTextView, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show() + Snackbar.make(logcatWebView, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show() } catch (exception: Exception) { // Display a snackbar with the error message. - Snackbar.make(logcatTextView, getString(R.string.error_saving_logcat, exception.toString()), Snackbar.LENGTH_INDEFINITE).show() + Snackbar.make(logcatWebView, getString(R.string.error_saving_logcat, exception.toString()), Snackbar.LENGTH_INDEFINITE).show() } } } @@ -117,25 +113,22 @@ class LogcatActivity : AppCompatActivity() { val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false) // Disable screenshots if not allowed. - if (!allowScreenshots) { + if (!allowScreenshots) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } // Run the default commands. super.onCreate(savedInstanceState) // Set the content view. - if (bottomAppBar) { + if (bottomAppBar) setContentView(R.layout.logcat_bottom_appbar) - } else { + else setContentView(R.layout.logcat_top_appbar) - } // Get handles for the views. val toolbar = findViewById(R.id.toolbar) swipeRefreshLayout = findViewById(R.id.swiperefreshlayout) - logcatScrollView = findViewById(R.id.scrollview) - logcatTextView = findViewById(R.id.logcat_textview) + logcatWebView = findViewById(R.id.logcat_webview) // Set the toolbar as the action bar. setSupportActionBar(toolbar) @@ -148,8 +141,8 @@ class LogcatActivity : AppCompatActivity() { // Implement swipe to refresh. swipeRefreshLayout.setOnRefreshListener { - // Get the current logcat. - getLogcat() + // Populate the current logcat. + populateLogcat() } // Set the swipe refresh color scheme according to the theme. @@ -167,14 +160,15 @@ class LogcatActivity : AppCompatActivity() { // Set the swipe refresh background color. swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt) - // Check to see if the activity has been restarted. - if (savedInstanceState != null) { - // Get the saved scrollview position. - scrollViewYPositionInt = savedInstanceState.getInt(SCROLLVIEW_POSITION) - } + // Restore the WebView scroll position if the activity has been restarted. + if (savedInstanceState != null) + logcatWebView.scrollY = savedInstanceState.getInt(SCROLL_Y) + + // Allow loading of file:// URLs. + logcatWebView.settings.allowFileAccess = true - // Get the logcat. - getLogcat() + // Populate the logcat. + populateLogcat() } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -193,14 +187,14 @@ class LogcatActivity : AppCompatActivity() { val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager // Save the logcat in a clip data. - val logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.text) + val logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatPlainTextStringBuilder) // Place the clip data on the clipboard. clipboardManager.setPrimaryClip(logcatClipData) // Display a snackbar if the API <= 32 (Android 12L). Beginning in Android 13 the OS displays a notification that covers up the snackbar. if (Build.VERSION.SDK_INT <= 32) - Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show() + Snackbar.make(logcatWebView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show() // Consume the event. true @@ -222,11 +216,8 @@ class LogcatActivity : AppCompatActivity() { // Wait for the process to finish. process.waitFor() - // Reset the scroll view Y position int. - scrollViewYPositionInt = 0 - // Reload the logcat. - getLogcat() + populateLogcat() } catch (exception: Exception) { // Do nothing. } @@ -246,14 +237,11 @@ class LogcatActivity : AppCompatActivity() { // Run the default commands. super.onSaveInstanceState(savedInstanceState) - // Get the scrollview Y position. - val scrollViewYPositionInt = logcatScrollView.scrollY - - // Store the scrollview Y position in the bundle. - savedInstanceState.putInt(SCROLLVIEW_POSITION, scrollViewYPositionInt) + // Store the scroll Y position in the bundle. + savedInstanceState.putInt(SCROLL_Y, logcatWebView.scrollY) } - private fun getLogcat() { + private fun populateLogcat() { try { // Get the logcat. `-b all` gets all the buffers (instead of just crash, main, and system). `-v long` produces more complete information. `-d` dumps the logcat and exits. val getLogcatProcess = Runtime.getRuntime().exec("logcat -b all -v long -d") @@ -261,8 +249,83 @@ class LogcatActivity : AppCompatActivity() { // Wrap the logcat in a buffered reader. val logcatBufferedReader = BufferedReader(InputStreamReader(getLogcatProcess.inputStream)) - // Display the logcat. - logcatTextView.text = logcatBufferedReader.readText() + // Reset the logcat plain text string. + logcatPlainTextStringBuilder = StringBuilder() + + // Create a logcat HTML string builder. + val logcatHtmlStringBuilder = StringBuilder() + + // Populate the initial HTML. + logcatHtmlStringBuilder.append("") + logcatHtmlStringBuilder.append("") + logcatHtmlStringBuilder.append("") + + // Respect dark mode. + logcatHtmlStringBuilder.append("") + + // Start the HTML body. + logcatHtmlStringBuilder.append("") + logcatHtmlStringBuilder.append("") + + // Create a logcat line string. + var logcatLineString: String? + + while (logcatBufferedReader.readLine().also { logcatLineString = it } != null) { + // Populate the logcat plain text string builder. + logcatPlainTextStringBuilder.append(logcatLineString) + + // Add a line break. + logcatPlainTextStringBuilder.append("\n") + + // Trim the string, which is necessary for correct detection of lines that start with `at`. + logcatLineString = logcatLineString!!.trim() + + // Apply syntax highlighting to the logcat. + if (logcatLineString!!.contains("crash") || logcatLineString!!.contains("Exception") ) { // Colorize crashes. + logcatHtmlStringBuilder.append("") + logcatHtmlStringBuilder.append(logcatLineString) + logcatHtmlStringBuilder.append("") + } else if (logcatLineString!!.startsWith("at") || logcatLineString!!.startsWith("Process:") || logcatLineString!!.contains("FATAL")) { // Colorize lines relating to crashes. + logcatHtmlStringBuilder.append("") + logcatHtmlStringBuilder.append(logcatLineString) + logcatHtmlStringBuilder.append("") + } else if (logcatLineString!!.startsWith("-")) { // Colorize the headers. + logcatHtmlStringBuilder.append("") + logcatHtmlStringBuilder.append(logcatLineString) + logcatHtmlStringBuilder.append("") + } else if (logcatLineString!!.startsWith("[ ")) { // Colorize the time stamps. + logcatHtmlStringBuilder.append("") + logcatHtmlStringBuilder.append(logcatLineString) + logcatHtmlStringBuilder.append("") + } else { // Display the standard lines. + logcatHtmlStringBuilder.append(logcatLineString) + } + + // Add a line break. + logcatHtmlStringBuilder.append("
") + } + + // Close the HTML. + logcatHtmlStringBuilder.append("") + logcatHtmlStringBuilder.append("") + + // Encode the logcat HTML. + val base64EncodedLogcatHtml: String = Base64.encodeToString(logcatHtmlStringBuilder.toString().toByteArray(Charsets.UTF_8), Base64.NO_PADDING) + + // Load the encoded logcat. + logcatWebView.loadData(base64EncodedLogcatHtml, "text/html", "base64") // Close the buffered reader. logcatBufferedReader.close() @@ -270,12 +333,6 @@ class LogcatActivity : AppCompatActivity() { // Do nothing. } - // Update the scroll position after the text is populated. - logcatTextView.post { - // Set the scroll position. - logcatScrollView.scrollY = scrollViewYPositionInt - } - // Stop the swipe to refresh animation if it is displayed. swipeRefreshLayout.isRefreshing = false }