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=23e6d525d44dfecd441d60a60f325999622df2dd;hp=0000000000000000000000000000000000000000;hb=322b36f275782a06ed66b950083f28cc37f5690a;hpb=1d656c562831f535aa33903d44198dd890393f4f diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt new file mode 100644 index 00000000..23e6d525 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt @@ -0,0 +1,321 @@ +/* + * Copyright © 2019-2021 Soren Stoutner . + * + * This file is part of Privacy Browser . + * + * Privacy Browser 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 Browser 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 Browser. If not, see . + */ + +package com.stoutner.privacybrowser.activities + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.OpenableColumns +import android.util.TypedValue +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.EditText +import android.widget.TextView +import android.widget.ScrollView + +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout + +import com.google.android.material.snackbar.Snackbar + +import com.stoutner.privacybrowser.R +import com.stoutner.privacybrowser.dialogs.SaveDialog + +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(), SaveDialog.SaveListener { + // 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 + + public override fun onCreate(savedInstanceState: Bundle?) { + // Get a handle for the shared preferences. + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + + // Get the preferences. + val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) + val bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false) + + // Disable screenshots if not allowed. + if (!allowScreenshots) { + window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + + // Set the theme. + setTheme(R.style.PrivacyBrowser) + + // Run the default commands. + super.onCreate(savedInstanceState) + + // 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. + // Instantiate the save alert dialog. + val saveDialogFragment: DialogFragment = SaveDialog.save(SaveDialog.SAVE_LOGCAT) + + // Show the save alert dialog. + saveDialogFragment.show(supportFragmentManager, getString(R.string.save_logcat)) + + // 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 + } + + // The activity result is called after browsing for a file in the save alert dialog. + public override fun onActivityResult(requestCode: Int, resultCode: Int, returnedIntent: Intent?) { + // Run the default commands. + super.onActivityResult(requestCode, resultCode, returnedIntent) + + // Only do something if the user didn't press back from the file picker. + if (resultCode == RESULT_OK) { + // Get a handle for the save dialog fragment. + val saveDialogFragment = supportFragmentManager.findFragmentByTag(getString(R.string.save_logcat)) as DialogFragment? + + // Only update the file name if the dialog still exists. + if (saveDialogFragment != null) { + // Get a handle for the save dialog. + val saveDialog = saveDialogFragment.dialog!! + + // Get a handle for the file name edit text. + val fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext) + + // Get the file name URI from the intent. + val fileNameUri = returnedIntent!!.data + + // Get the file name string from the URI. + val fileNameString = fileNameUri.toString() + + // Set the file name text. + fileNameEditText.setText(fileNameString) + + // Move the cursor to the end of the file name edit text. + fileNameEditText.setSelection(fileNameString.length) + } + } + } + + override fun onSave(saveType: Int, dialogFragment: DialogFragment) { + // Get a handle for the dialog. + val dialog = dialogFragment.dialog!! + + // Get a handle for the file name edit text. + val fileNameEditText = dialog.findViewById(R.id.file_name_edittext) + + // Get the file path string. + var fileNameString = fileNameEditText.text.toString() + + try { + // Get the logcat as a string. + val logcatString = logcatTextView.text.toString() + + // Open an output stream. + val outputStream = contentResolver.openOutputStream(Uri.parse(fileNameString))!! + + // Write the logcat string to the output stream. + outputStream.write(logcatString.toByteArray(StandardCharsets.UTF_8)) + + // Close the output stream. + outputStream.close() + + // Get the actual file name if the API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + // Get a cursor from the content resolver. + val contentResolverCursor = contentResolver.query(Uri.parse(fileNameString), null, null, null)!! + + // Move to the first row. + contentResolverCursor.moveToFirst() + + // Get the file name from the cursor. + 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.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show() + } catch (exception: Exception) { + // Display a snackbar with the error message. + Snackbar.make(logcatTextView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show() + } + } +} \ No newline at end of file