X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FLogcatActivity.java;h=3200c2be0b6fbfae60966ad6edcf12ef87570e1a;hp=a615a541dd45c9be93e45904ce23925ed3313a71;hb=39380e8e8bdb3b9e29569a263277c9c3112b44ac;hpb=ba40295dffd761ccdc95d3b46ca7acbad1f00d5e diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java index a615a541..3200c2be 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Soren Stoutner . + * Copyright © 2019-2020 Soren Stoutner . * * This file is part of Privacy Browser . * @@ -24,32 +24,42 @@ import android.app.Activity; import android.app.Dialog; import android.content.ClipData; import android.content.ClipboardManager; +import android.content.ContentResolver; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.media.MediaScannerConnection; import android.net.Uri; -import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; -import android.os.Environment; +import android.preference.PreferenceManager; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.view.WindowManager; import android.widget.EditText; +import android.widget.ScrollView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; // The AndroidX toolbar must be used until the minimum API is >= 21. +import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; import androidx.fragment.app.DialogFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.snackbar.Snackbar; + import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.asynctasks.GetLogcat; import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog; -import com.stoutner.privacybrowser.dialogs.SaveLogcatDialog; +import com.stoutner.privacybrowser.dialogs.SaveDialog; +import com.stoutner.privacybrowser.helpers.FileNameHelper; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -60,25 +70,33 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; -import java.lang.ref.WeakReference; import java.nio.charset.StandardCharsets; -public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialog.SaveLogcatListener, StoragePermissionDialog.StoragePermissionDialogListener { +public class LogcatActivity extends AppCompatActivity implements SaveDialog.SaveListener, StoragePermissionDialog.StoragePermissionDialogListener { + // Declare the class constants. + private final String SCROLLVIEW_POSITION = "scrollview_position"; + + // Declare the class variables. private String filePathString; + // Define the class views. + private TextView logcatTextView; + @Override public void onCreate(Bundle savedInstanceState) { + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Get the screenshot preference. + boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false); + // Disable screenshots if not allowed. - if (!MainWebViewActivity.allowScreenshots) { + if (!allowScreenshots) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); } - // Set the activity theme. - if (MainWebViewActivity.darkTheme) { - setTheme(R.style.PrivacyBrowserDark_SecondaryActivity); - } else { - setTheme(R.style.PrivacyBrowserLight_SecondaryActivity); - } + // Set the theme. + setTheme(R.style.PrivacyBrowser); // Run the default commands. super.onCreate(savedInstanceState); @@ -86,7 +104,7 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo // Set the content view. setContentView(R.layout.logcat_coordinatorlayout); - // The AndroidX toolbar must be used until the minimum API is >= 21. + // Set the toolbar as the action bar. Toolbar toolbar = findViewById(R.id.logcat_toolbar); setSupportActionBar(toolbar); @@ -99,23 +117,49 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo // Display the the back arrow in the action bar. actionBar.setDisplayHomeAsUpEnabled(true); + // Populate the class views. + logcatTextView = findViewById(R.id.logcat_textview); + // Implement swipe to refresh. SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.logcat_swiperefreshlayout); swipeRefreshLayout.setOnRefreshListener(() -> { // Get the current logcat. - new GetLogcat(this).execute(); + new GetLogcat(this, 0).execute(); }); - // Set the swipe to refresh color according to the theme. - if (MainWebViewActivity.darkTheme) { - swipeRefreshLayout.setColorSchemeResources(R.color.blue_600); - swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_800); - } else { + // Get the current theme status. + int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + + // Set the refresh color scheme according to the theme. + if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); + } else { + swipeRefreshLayout.setColorSchemeResources(R.color.blue_500); + } + + // Initialize a color background typed value. + TypedValue colorBackgroundTypedValue = new TypedValue(); + + // Get the color background from the theme. + getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true); + + // Get the color background int from the typed value. + int colorBackgroundInt = colorBackgroundTypedValue.data; + + // Set the swipe refresh background color. + swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt); + + // Initialize the scrollview Y position int. + int scrollViewYPositionInt = 0; + + // 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. - new GetLogcat(this).execute(); + new GetLogcat(this, scrollViewYPositionInt).execute(); } @Override @@ -133,66 +177,80 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo int menuItemId = menuItem.getItemId(); // Run the commands that correlate to the selected menu item. - switch (menuItemId) { - case R.id.copy: - // Get a handle for the clipboard manager. - ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + if (menuItemId == R.id.copy) { // Copy was selected. + // Get a handle for the clipboard manager. + ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - // Get a handle for the logcat text view. - TextView logcatTextView = findViewById(R.id.logcat_textview); + // Remove the incorrect lint error below that the clipboard manager might be null. + assert clipboardManager != null; - // Save the logcat in a ClipData. - ClipData logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.getText()); + // Save the logcat in a clip data. + ClipData logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.getText()); - // Remove the incorrect lint error that `clipboardManager.setPrimaryClip()` might produce a null pointer exception. - assert clipboardManager != null; + // Place the clip data on the clipboard. + clipboardManager.setPrimaryClip(logcatClipData); - // Place the ClipData on the clipboard. - clipboardManager.setPrimaryClip(logcatClipData); + // Display a snackbar. + Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show(); - // Display a snackbar. - Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show(); + // Consume the event. + return true; + } else if (menuItemId == R.id.save) { // Save was selected. + // Instantiate the save alert dialog. + DialogFragment saveDialogFragment = SaveDialog.save(SaveDialog.SAVE_LOGCAT); - // Consume the event. - return true; + // Show the save alert dialog. + saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_logcat)); - case R.id.save: - // Get a handle for the save alert dialog. - DialogFragment saveDialogFragment = new SaveLogcatDialog(); + // Consume the event. + return true; + } else if (menuItemId == 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). + Process process = Runtime.getRuntime().exec("logcat -b all -c"); - // Show the save alert dialog. - saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_logcat)); + // Wait for the process to finish. + process.waitFor(); - // Consume the event. - return true; + // Reload the logcat. + new GetLogcat(this, 0).execute(); + } catch (IOException | InterruptedException exception) { + // Do nothing. + } - case R.id.clear: - try { - // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system). - Process process = Runtime.getRuntime().exec("logcat -b all -c"); + // Consume the event. + return true; + } else { // The home button was pushed. + // Do not consume the event. The system will process the home command. + return super.onOptionsItemSelected(menuItem); + } + } - // Wait for the process to finish. - process.waitFor(); + @Override + public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { + // Run the default commands. + super.onSaveInstanceState(savedInstanceState); - // Reload the logcat. - new GetLogcat(this).execute(); - } catch (IOException|InterruptedException exception) { - // Do nothing. - } + // Get a handle for the logcat scrollview. + ScrollView logcatScrollView = findViewById(R.id.logcat_scrollview); - // Consume the event. - return true; + // Get the scrollview Y position. + int scrollViewYPositionInt = logcatScrollView.getScrollY(); - default: - // Don't consume the event. - return super.onOptionsItemSelected(menuItem); - } + // Store the scrollview Y position in the bundle. + savedInstanceState.putInt(SCROLLVIEW_POSITION, scrollViewYPositionInt); } @Override - public void onSaveLogcat(DialogFragment dialogFragment) { + public void onSave(int saveType, DialogFragment dialogFragment) { + // Get a handle for the dialog. + Dialog dialog = dialogFragment.getDialog(); + + // Remove the lint warning below that the dialog might be null. + assert dialog != null; + // Get a handle for the file name edit text. - EditText fileNameEditText = dialogFragment.getDialog().findViewById(R.id.file_name_edittext); + EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext); // Get the file path string. filePathString = fileNameEditText.getText().toString(); @@ -202,7 +260,7 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo // Save the logcat. saveLogcat(filePathString); } else { // The storage permission has not been granted. - // Get the external private directory `File`. + // Get the external private directory file. File externalPrivateDirectoryFile = getExternalFilesDir(null); // Remove the incorrect lint error below that the file might be null. @@ -215,16 +273,16 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo if (filePathString.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory. // Save the logcat. saveLogcat(filePathString); - } else { // The file path in in a public directory. + } else { // The file path is in a public directory. // Check if the user has previously denied the storage permission. if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first. - // Instantiate the storage permission alert dialog. - DialogFragment storagePermissionDialogFragment = new StoragePermissionDialog(); + // Instantiate the storage permission alert dialog. The type is specified as `0` because it currently isn't used for this activity. + DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(0); // Show the storage permission alert dialog. The permission will be requested when the dialog is closed. storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission)); } else { // Show the permission request directly. - // Request the storage permission. The logcat will be saved when it finishes. + // Request the write external storage permission. The logcat will be saved when it finishes. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); } @@ -233,7 +291,7 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo } @Override - public void onCloseStoragePermissionDialog() { + public void onCloseStoragePermissionDialog(int requestType) { // Request the write external storage permission. The logcat will be saved when it finishes. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); } @@ -245,18 +303,59 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo // Save the logcat. saveLogcat(filePathString); } else { // The storage permission was not granted. - // Get a handle for the logcat text view. - TextView logcatTextView = findViewById(R.id.logcat_textview); - // Display an error snackbar. Snackbar.make(logcatTextView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); } } - private void saveLogcat(String fileNameString) { - // Get a handle for the logcat text view. - TextView logcatTextView = findViewById(R.id.logcat_textview); + // The activity result is called after browsing for a file in the save alert dialog. + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // Run the default commands. + super.onActivityResult(requestCode, resultCode, data); + + // Only do something if the user didn't press back from the file picker. + if (resultCode == Activity.RESULT_OK) { + // Get a handle for the save dialog fragment. + DialogFragment saveDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_logcat)); + + // Only update the file name if the dialog still exists. + if (saveDialogFragment != null) { + // Get a handle for the save dialog. + Dialog saveDialog = saveDialogFragment.getDialog(); + + // Remove the lint warning below that the save dialog might be null. + assert saveDialog != null; + + // Get a handle for the dialog views. + EditText fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext); + TextView fileExistsWarningTextView = saveDialog.findViewById(R.id.file_exists_warning_textview); + + // Get the file name URI from the intent. + Uri fileNameUri = data.getData(); + + // Process the file name URI if it is not null. + if (fileNameUri != null) { + // Instantiate a file name helper. + FileNameHelper fileNameHelper = new FileNameHelper(); + + // Convert the file name URI to a file name path. + String fileNamePath = fileNameHelper.convertUriToFileNamePath(fileNameUri); + + // Set the file name path as the text of the file name edit text. + fileNameEditText.setText(fileNamePath); + + // Move the cursor to the end of the file name edit text. + fileNameEditText.setSelection(fileNamePath.length()); + + // Hide the file exists warning. + fileExistsWarningTextView.setVisibility(View.GONE); + } + } + } + } + private void saveLogcat(String fileNameString) { try { // Get the logcat as a string. String logcatString = logcatTextView.getText().toString(); @@ -270,6 +369,12 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo // Create a file from the file name string. File saveFile = new File(fileNameString); + // Delete the file if it already exists. + if (saveFile.exists()) { + //noinspection ResultOfMethodCallIgnored + saveFile.delete(); + } + // Create a file buffered writer. BufferedWriter fileBufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(saveFile))); @@ -292,157 +397,45 @@ public class LogcatActivity extends AppCompatActivity implements SaveLogcatDialo // Add the file to the list of recent files. This doesn't currently work, but maybe it will someday. MediaScannerConnection.scanFile(this, new String[] {fileNameString}, new String[] {"text/plain"}, null); - // Display a snackbar. - Snackbar.make(logcatTextView, getString(R.string.file_saved_successfully), Snackbar.LENGTH_SHORT).show(); - } catch (Exception exception) { - // Display a snackbar with the error message. - Snackbar.make(logcatTextView, getString(R.string.save_failed) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show(); - } - } - - // The activity result is called after browsing for a file in the save alert dialog. - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - // Don't do anything if the user pressed back from the file picker. - if (resultCode == Activity.RESULT_OK) { - // Get a handle for the save dialog fragment. - DialogFragment saveDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_logcat)); - - // Remove the incorrect lint error that the save dialog fragment might be null. - assert saveDialogFragment != null; - - // Get a handle for the save dialog. - Dialog saveDialog = saveDialogFragment.getDialog(); - - // Get a handle for the file name edit text. - EditText fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext); - - // Get the file name URI. - Uri fileNameUri = data.getData(); - - // Remove the incorrect lint warning that the file name URI might be null. - assert fileNameUri != null; - - // Get the raw file name path. - String rawFileNamePath = fileNameUri.getPath(); - - // Remove the incorrect lint warning that the file name path might be null. - assert rawFileNamePath != null; - - // Check to see if the file name Path includes a valid storage location. - if (rawFileNamePath.contains(":")) { // The path is valid. - // Split the path into the initial content uri and the final path information. - String fileNameContentPath = rawFileNamePath.substring(0, rawFileNamePath.indexOf(":")); - String fileNameFinalPath = rawFileNamePath.substring(rawFileNamePath.indexOf(":") + 1); - - // Create the file name path string. - String fileNamePath; - - // Construct the file name path. - switch (fileNameContentPath) { - // The documents home has a special content path. - case "/document/home": - fileNamePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + fileNameFinalPath; - break; + // Create a logcat saved snackbar. + Snackbar logcatSavedSnackbar = Snackbar.make(logcatTextView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT); - // Everything else for the primary user should be in `/document/primary`. - case "/document/primary": - fileNamePath = Environment.getExternalStorageDirectory() + "/" + fileNameFinalPath; - break; + // Add an open action to the snackbar. + logcatSavedSnackbar.setAction(R.string.open, (View view) -> { + // Get a file for the file name string. + File file = new File(fileNameString); - // Just in case, catch everything else and place it in the external storage directory. - default: - fileNamePath = Environment.getExternalStorageDirectory() + "/" + fileNameFinalPath; - break; - } - - // Set the file name path as the text of the file name edit text. - fileNameEditText.setText(fileNamePath); - } else { // The path is invalid. - // Close the alert dialog. - saveDialog.dismiss(); - - // Get a handle for the logcat text view. - TextView logcatTextView = findViewById(R.id.logcat_textview); - - // Display a snackbar with the error message. - Snackbar.make(logcatTextView, rawFileNamePath + " " + getString(R.string.invalid_location), Snackbar.LENGTH_INDEFINITE).show(); - } - } - } - - // `Void` does not declare any parameters. `Void` does not declare progress units. `String` contains the results. - private static class GetLogcat extends AsyncTask { - // Create a weak reference to the calling activity. - private final WeakReference activityWeakReference; - - // Populate the weak reference to the calling activity. - GetLogcat(Activity activity) { - activityWeakReference = new WeakReference<>(activity); - } - - @Override - protected String doInBackground(Void... parameters) { - // Get a handle for the activity. - Activity activity = activityWeakReference.get(); - - // Abort if the activity is gone. - if ((activity == null) || activity.isFinishing()) { - return ""; - } + // Declare a file URI variable. + Uri fileUri; - // Create a log string builder. - StringBuilder logStringBuilder = new StringBuilder(); - - 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. - Process process = Runtime.getRuntime().exec("logcat -b all -v long -d"); - - // Wrap the logcat in a buffered reader. - BufferedReader logBufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); - - // Create a log transfer string. - String logTransferString; - - // Use the log transfer string to copy the logcat from the buffered reader to the string builder. - while ((logTransferString = logBufferedReader.readLine()) != null) { - // Append a line. - logStringBuilder.append(logTransferString); - - // Append a line break. - logStringBuilder.append("\n"); + // Get the URI for the file according to the Android version. + if (Build.VERSION.SDK_INT >= 24) { // Use a file provider. + fileUri = FileProvider.getUriForFile(this, getString(R.string.file_provider), file); + } else { // Get the raw file path URI. + fileUri = Uri.fromFile(file); } - // Close the buffered reader. - logBufferedReader.close(); - } catch (IOException exception) { - // Do nothing. - } - - // Return the logcat. - return logStringBuilder.toString(); - } + // Get a handle for the content resolver. + ContentResolver contentResolver = getContentResolver(); - // `onPostExecute()` operates on the UI thread. - @Override - protected void onPostExecute(String logcatString) { - // Get a handle for the activity. - Activity activity = activityWeakReference.get(); + // Create an open intent with `ACTION_VIEW`. + Intent openIntent = new Intent(Intent.ACTION_VIEW); - // Abort if the activity is gone. - if ((activity == null) || activity.isFinishing()) { - return; - } + // Set the URI and the MIME type. + openIntent.setDataAndType(fileUri, contentResolver.getType(fileUri)); - // Get handles for the views. - TextView logcatTextView = activity.findViewById(R.id.logcat_textview); - SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.logcat_swiperefreshlayout); + // Allow the app to read the file URI. + openIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - // Display the logcat. - logcatTextView.setText(logcatString); + // Show the chooser. + startActivity(Intent.createChooser(openIntent, getString(R.string.open))); + }); - // Stop the swipe to refresh animation if it is displayed. - swipeRefreshLayout.setRefreshing(false); + // Show the logcat saved snackbar. + logcatSavedSnackbar.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