X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyCell.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacycell%2Factivities%2FLogcatActivity.kt;fp=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacycell%2Factivities%2FLogcatActivity.kt;h=122ccc544364f1fe2b69108bb35c5d34f31fb57e;hp=0000000000000000000000000000000000000000;hb=45b64d4cf242b28abd6747681086f0a080fca009;hpb=cb92ea552a5ffa8ca3142053660e3a73afc9240a 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 index 0000000..122ccc5 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacycell/activities/LogcatActivity.kt @@ -0,0 +1,263 @@ +/* + * Copyright © 2021 Soren Stoutner . + * + * This file is part of 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 . + */ + +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(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