]> gitweb.stoutner.com Git - PrivacyCell.git/blobdiff - app/src/main/java/com/stoutner/privacycell/activities/LogcatActivity.kt
Add a logcat activity. https://redmine.stoutner.com/issues/768
[PrivacyCell.git] / app / src / main / java / com / stoutner / privacycell / activities / LogcatActivity.kt
diff --git a/app/src/main/java/com/stoutner/privacycell/activities/LogcatActivity.kt b/app/src/main/java/com/stoutner/privacycell/activities/LogcatActivity.kt
new file mode 100644 (file)
index 0000000..122ccc5
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * Copyright © 2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
+ *
+ * Privacy Cell is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Cell is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Cell.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacycell.activities
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.net.Uri
+import android.os.Bundle
+import android.provider.OpenableColumns
+import android.util.TypedValue
+import android.view.Menu
+import android.view.MenuItem
+import android.widget.ScrollView
+import android.widget.TextView
+
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.Toolbar
+import androidx.preference.PreferenceManager
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+
+import com.google.android.material.snackbar.Snackbar
+
+import com.stoutner.privacycell.R
+
+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"
+
+class LogcatActivity : AppCompatActivity() {
+    // Define the class variables.
+    private var scrollViewYPositionInt = 0
+
+    // Define the class views.
+    private lateinit var swipeRefreshLayout: SwipeRefreshLayout
+    private lateinit var logcatScrollView: ScrollView
+    private lateinit var logcatTextView: TextView
+
+    // Define the save logcat activity result launcher.
+    private val saveLogcatActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) { fileNameUri: Uri? ->
+        // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back.
+        if (fileNameUri != null) {
+            try {
+                // Get the logcat as a string.
+                val logcatString = logcatTextView.text.toString()
+
+                // Open an output stream.
+                val outputStream = contentResolver.openOutputStream(fileNameUri)!!
+
+                // Write the logcat string to the output stream.
+                outputStream.write(logcatString.toByteArray(StandardCharsets.UTF_8))
+
+                // Close the output stream.
+                outputStream.close()
+
+                // Get a cursor from the content resolver.
+                val contentResolverCursor = contentResolver.query(fileNameUri, null, null, null)!!
+
+                // Move to the first row.
+                contentResolverCursor.moveToFirst()
+
+                // Get the file name from the cursor.
+                val fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+
+                // Close the cursor.
+                contentResolverCursor.close()
+
+                // Display a snackbar with the saved logcat information.
+                Snackbar.make(logcatTextView, getString(R.string.logcat_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()
+            }
+        }
+    }
+
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        // Run the default commands.
+        super.onCreate(savedInstanceState)
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
+
+        // Get the bottom app bar preference.
+        val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false)
+
+        // Set the content view.
+        if (bottomAppBar) {
+            setContentView(R.layout.logcat_bottom_appbar)
+        } else {
+            setContentView(R.layout.logcat_top_appbar)
+        }
+
+        // Get handles for the views.
+        val toolbar = findViewById<Toolbar>(R.id.toolbar)
+        swipeRefreshLayout = findViewById(R.id.swiperefreshlayout)
+        logcatScrollView = findViewById(R.id.scrollview)
+        logcatTextView = findViewById(R.id.logcat_textview)
+
+        // Set the toolbar as the action bar.
+        setSupportActionBar(toolbar)
+
+        // Get a handle for the action bar.
+        val actionBar = supportActionBar!!
+
+        // Display the back arrow in the action bar.
+        actionBar.setDisplayHomeAsUpEnabled(true)
+
+        // Implement swipe to refresh.
+        swipeRefreshLayout.setOnRefreshListener {
+            // Get the current logcat.
+            getLogcat()
+        }
+
+        // Set the swipe refresh color scheme according to the theme.
+        swipeRefreshLayout.setColorSchemeResources(R.color.blue_text)
+
+        // Initialize a color background typed value.
+        val colorBackgroundTypedValue = TypedValue()
+
+        // Get the color background from the theme.
+        theme.resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true)
+
+        // Get the color background int from the typed value.
+        val colorBackgroundInt = colorBackgroundTypedValue.data
+
+        // 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)
+        }
+
+        // Get the logcat.
+        getLogcat()
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu): Boolean {
+        // Inflate the menu.  This adds items to the action bar.
+        menuInflater.inflate(R.menu.logcat_options_menu, menu)
+
+        // Display the menu.
+        return true
+    }
+
+    override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
+        // Run the commands that correlate to the selected menu item.
+        return when (menuItem.itemId) {
+            R.id.copy -> {  // Copy was selected.
+                // Get a handle for the clipboard manager.
+                val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
+
+                // Save the logcat in a clip data.
+                val logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.text)
+
+                // Place the clip data on the clipboard.
+                clipboardManager.setPrimaryClip(logcatClipData)
+
+                // Display a snackbar.
+                Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show()
+
+                // Consume the event.
+                true
+            }
+
+            R.id.save -> {  // Save was selected.
+                // Open the file picker.
+                saveLogcatActivityResultLauncher.launch(getString(R.string.privacy_cell_logcat_txt))
+
+                // Consume the event.
+                true
+            }
+
+            R.id.clear -> {  // Clear was selected.
+                try {
+                    // Clear the logcat.  `-c` clears the logcat.  `-b all` clears all the buffers (instead of just crash, main, and system).
+                    val process = Runtime.getRuntime().exec("logcat -b all -c")
+
+                    // Wait for the process to finish.
+                    process.waitFor()
+
+                    // Reset the scroll view Y position int.
+                    scrollViewYPositionInt = 0
+
+                    // Reload the logcat.
+                    getLogcat()
+                } catch (exception: Exception) {
+                    // Do nothing.
+                }
+
+                // Consume the event.
+                true
+            }
+
+            else -> {  // The home button was pushed.
+                // Do not consume the event.  The system will process the home command.
+                super.onOptionsItemSelected(menuItem)
+            }
+        }
+    }
+
+    public override fun onSaveInstanceState(savedInstanceState: Bundle) {
+        // 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)
+    }
+
+    private fun getLogcat() {
+        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")
+
+            // Wrap the logcat in a buffered reader.
+            val logcatBufferedReader = BufferedReader(InputStreamReader(getLogcatProcess.inputStream))
+
+            // Display the logcat.
+            logcatTextView.text = logcatBufferedReader.readText()
+
+            // Close the buffered reader.
+            logcatBufferedReader.close()
+        } catch (exception: IOException) {
+            // 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
+    }
+}
\ No newline at end of file