2 * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.activities;
22 import android.app.Activity;
23 import android.app.Dialog;
24 import android.content.ClipData;
25 import android.content.ClipboardManager;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.res.Configuration;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.preference.PreferenceManager;
32 import android.util.TypedValue;
33 import android.view.Menu;
34 import android.view.MenuItem;
35 import android.view.WindowManager;
36 import android.widget.EditText;
37 import android.widget.ScrollView;
38 import android.widget.TextView;
40 import androidx.annotation.NonNull;
41 import androidx.appcompat.app.ActionBar;
42 import androidx.appcompat.app.AppCompatActivity;
43 import androidx.appcompat.widget.Toolbar;
44 import androidx.fragment.app.DialogFragment;
45 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
47 import com.google.android.material.snackbar.Snackbar;
49 import com.stoutner.privacybrowser.R;
50 import com.stoutner.privacybrowser.asynctasks.GetLogcat;
51 import com.stoutner.privacybrowser.dialogs.SaveDialog;
53 import java.io.BufferedReader;
54 import java.io.BufferedWriter;
55 import java.io.ByteArrayInputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.io.InputStreamReader;
59 import java.io.OutputStream;
60 import java.io.OutputStreamWriter;
61 import java.nio.charset.StandardCharsets;
63 public class LogcatActivity extends AppCompatActivity implements SaveDialog.SaveListener {
64 // Declare the class constants.
65 private final String SCROLLVIEW_POSITION = "scrollview_position";
67 // Define the class views.
68 private TextView logcatTextView;
71 public void onCreate(Bundle savedInstanceState) {
72 // Get a handle for the shared preferences.
73 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
75 // Get the preferences.
76 boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
77 boolean bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
79 // Disable screenshots if not allowed.
80 if (!allowScreenshots) {
81 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
85 setTheme(R.style.PrivacyBrowser);
87 // Run the default commands.
88 super.onCreate(savedInstanceState);
90 // Set the content view.
92 setContentView(R.layout.logcat_coordinatorlayout_bottom_appbar);
94 setContentView(R.layout.logcat_coordinatorlayout_top_appbar);
97 // Get handles for the views.
98 Toolbar toolbar = findViewById(R.id.logcat_toolbar);
99 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.logcat_swiperefreshlayout);
101 // Set the toolbar as the action bar.
102 setSupportActionBar(toolbar);
104 // Get a handle for the action bar.
105 ActionBar actionBar = getSupportActionBar();
107 // Remove the incorrect lint warning that the action bar might be null.
108 assert actionBar != null;
110 // Display the the back arrow in the action bar.
111 actionBar.setDisplayHomeAsUpEnabled(true);
113 // Populate the class views.
114 logcatTextView = findViewById(R.id.logcat_textview);
116 // Implement swipe to refresh.
117 swipeRefreshLayout.setOnRefreshListener(() -> {
118 // Get the current logcat.
119 new GetLogcat(this, 0).execute();
122 // Get the current theme status.
123 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
125 // Set the refresh color scheme according to the theme.
126 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
127 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
129 swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
132 // Initialize a color background typed value.
133 TypedValue colorBackgroundTypedValue = new TypedValue();
135 // Get the color background from the theme.
136 getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
138 // Get the color background int from the typed value.
139 int colorBackgroundInt = colorBackgroundTypedValue.data;
141 // Set the swipe refresh background color.
142 swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
144 // Initialize the scrollview Y position int.
145 int scrollViewYPositionInt = 0;
147 // Check to see if the activity has been restarted.
148 if (savedInstanceState != null) {
149 // Get the saved scrollview position.
150 scrollViewYPositionInt = savedInstanceState.getInt(SCROLLVIEW_POSITION);
154 new GetLogcat(this, scrollViewYPositionInt).execute();
158 public boolean onCreateOptionsMenu(Menu menu) {
159 // Inflate the menu. This adds items to the action bar.
160 getMenuInflater().inflate(R.menu.logcat_options_menu, menu);
167 public boolean onOptionsItemSelected(MenuItem menuItem) {
168 // Get the selected menu item ID.
169 int menuItemId = menuItem.getItemId();
171 // Run the commands that correlate to the selected menu item.
172 if (menuItemId == R.id.copy) { // Copy was selected.
173 // Get a handle for the clipboard manager.
174 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
176 // Remove the incorrect lint error below that the clipboard manager might be null.
177 assert clipboardManager != null;
179 // Save the logcat in a clip data.
180 ClipData logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.getText());
182 // Place the clip data on the clipboard.
183 clipboardManager.setPrimaryClip(logcatClipData);
185 // Display a snackbar.
186 Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show();
188 // Consume the event.
190 } else if (menuItemId == R.id.save) { // Save was selected.
191 // Instantiate the save alert dialog.
192 DialogFragment saveDialogFragment = SaveDialog.save(SaveDialog.SAVE_LOGCAT);
194 // Show the save alert dialog.
195 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_logcat));
197 // Consume the event.
199 } else if (menuItemId == R.id.clear) { // Clear was selected.
201 // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system).
202 Process process = Runtime.getRuntime().exec("logcat -b all -c");
204 // Wait for the process to finish.
207 // Reload the logcat.
208 new GetLogcat(this, 0).execute();
209 } catch (IOException | InterruptedException exception) {
213 // Consume the event.
215 } else { // The home button was pushed.
216 // Do not consume the event. The system will process the home command.
217 return super.onOptionsItemSelected(menuItem);
222 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
223 // Run the default commands.
224 super.onSaveInstanceState(savedInstanceState);
226 // Get a handle for the logcat scrollview.
227 ScrollView logcatScrollView = findViewById(R.id.logcat_scrollview);
229 // Get the scrollview Y position.
230 int scrollViewYPositionInt = logcatScrollView.getScrollY();
232 // Store the scrollview Y position in the bundle.
233 savedInstanceState.putInt(SCROLLVIEW_POSITION, scrollViewYPositionInt);
236 // The activity result is called after browsing for a file in the save alert dialog.
238 public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
239 // Run the default commands.
240 super.onActivityResult(requestCode, resultCode, returnedIntent);
242 // Only do something if the user didn't press back from the file picker.
243 if (resultCode == Activity.RESULT_OK) {
244 // Get a handle for the save dialog fragment.
245 DialogFragment saveDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_logcat));
247 // Only update the file name if the dialog still exists.
248 if (saveDialogFragment != null) {
249 // Get a handle for the save dialog.
250 Dialog saveDialog = saveDialogFragment.getDialog();
252 // Remove the lint warning below that the save dialog might be null.
253 assert saveDialog != null;
255 // Get a handle for the file name edit text.
256 EditText fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext);
258 // Get the file name URI from the intent.
259 Uri fileNameUri = returnedIntent.getData();
261 // Get the file name string from the URI.
262 String fileNameString = fileNameUri.toString();
264 // Set the file name text.
265 fileNameEditText.setText(fileNameString);
267 // Move the cursor to the end of the file name edit text.
268 fileNameEditText.setSelection(fileNameString.length());
274 public void onSave(int saveType, DialogFragment dialogFragment) {
275 // Get a handle for the dialog.
276 Dialog dialog = dialogFragment.getDialog();
278 // Remove the lint warning below that the dialog might be null.
279 assert dialog != null;
281 // Get a handle for the file name edit text.
282 EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
284 // Get the file path string.
285 String fileNameString = fileNameEditText.getText().toString();
288 // Get the logcat as a string.
289 String logcatString = logcatTextView.getText().toString();
291 // Create an input stream with the contents of the logcat.
292 InputStream logcatInputStream = new ByteArrayInputStream(logcatString.getBytes(StandardCharsets.UTF_8));
294 // Create a logcat buffered reader.
295 BufferedReader logcatBufferedReader = new BufferedReader(new InputStreamReader(logcatInputStream));
297 // Open an output stream.
298 OutputStream outputStream = getContentResolver().openOutputStream(Uri.parse(fileNameString));
300 // Create a file buffered writer.
301 BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
303 // Create a transfer string.
304 String transferString;
306 // Use the transfer string to copy the logcat from the buffered reader to the buffered writer.
307 while ((transferString = logcatBufferedReader.readLine()) != null) {
308 // Append the line to the buffered writer.
309 bufferedWriter.append(transferString);
311 // Append a line break.
312 bufferedWriter.append("\n");
315 // Flush the buffered writer.
316 bufferedWriter.flush();
318 // Close the inputs and outputs.
319 logcatBufferedReader.close();
320 logcatInputStream.close();
321 bufferedWriter.close();
322 outputStream.close();
324 // Display a snackbar with the saved logcat information.
325 Snackbar.make(logcatTextView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show();
326 } catch (Exception exception) {
327 // Display a snackbar with the error message.
328 Snackbar.make(logcatTextView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();