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 screenshot preference.
76 boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
78 // Disable screenshots if not allowed.
79 if (!allowScreenshots) {
80 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
84 setTheme(R.style.PrivacyBrowser);
86 // Run the default commands.
87 super.onCreate(savedInstanceState);
89 // Set the content view.
90 setContentView(R.layout.logcat_coordinatorlayout);
92 // Get handles for the views.
93 Toolbar toolbar = findViewById(R.id.logcat_toolbar);
94 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.logcat_swiperefreshlayout);
96 // Set the toolbar as the action bar.
97 setSupportActionBar(toolbar);
99 // Get a handle for the action bar.
100 ActionBar actionBar = getSupportActionBar();
102 // Remove the incorrect lint warning that the action bar might be null.
103 assert actionBar != null;
105 // Display the the back arrow in the action bar.
106 actionBar.setDisplayHomeAsUpEnabled(true);
108 // Populate the class views.
109 logcatTextView = findViewById(R.id.logcat_textview);
111 // Implement swipe to refresh.
112 swipeRefreshLayout.setOnRefreshListener(() -> {
113 // Get the current logcat.
114 new GetLogcat(this, 0).execute();
117 // Get the current theme status.
118 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
120 // Set the refresh color scheme according to the theme.
121 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
122 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
124 swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
127 // Initialize a color background typed value.
128 TypedValue colorBackgroundTypedValue = new TypedValue();
130 // Get the color background from the theme.
131 getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
133 // Get the color background int from the typed value.
134 int colorBackgroundInt = colorBackgroundTypedValue.data;
136 // Set the swipe refresh background color.
137 swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
139 // Initialize the scrollview Y position int.
140 int scrollViewYPositionInt = 0;
142 // Check to see if the activity has been restarted.
143 if (savedInstanceState != null) {
144 // Get the saved scrollview position.
145 scrollViewYPositionInt = savedInstanceState.getInt(SCROLLVIEW_POSITION);
149 new GetLogcat(this, scrollViewYPositionInt).execute();
153 public boolean onCreateOptionsMenu(Menu menu) {
154 // Inflate the menu. This adds items to the action bar.
155 getMenuInflater().inflate(R.menu.logcat_options_menu, menu);
162 public boolean onOptionsItemSelected(MenuItem menuItem) {
163 // Get the selected menu item ID.
164 int menuItemId = menuItem.getItemId();
166 // Run the commands that correlate to the selected menu item.
167 if (menuItemId == R.id.copy) { // Copy was selected.
168 // Get a handle for the clipboard manager.
169 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
171 // Remove the incorrect lint error below that the clipboard manager might be null.
172 assert clipboardManager != null;
174 // Save the logcat in a clip data.
175 ClipData logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.getText());
177 // Place the clip data on the clipboard.
178 clipboardManager.setPrimaryClip(logcatClipData);
180 // Display a snackbar.
181 Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show();
183 // Consume the event.
185 } else if (menuItemId == R.id.save) { // Save was selected.
186 // Instantiate the save alert dialog.
187 DialogFragment saveDialogFragment = SaveDialog.save(SaveDialog.SAVE_LOGCAT);
189 // Show the save alert dialog.
190 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_logcat));
192 // Consume the event.
194 } else if (menuItemId == R.id.clear) { // Clear was selected.
196 // Clear the logcat. `-c` clears the logcat. `-b all` clears all the buffers (instead of just crash, main, and system).
197 Process process = Runtime.getRuntime().exec("logcat -b all -c");
199 // Wait for the process to finish.
202 // Reload the logcat.
203 new GetLogcat(this, 0).execute();
204 } catch (IOException | InterruptedException exception) {
208 // Consume the event.
210 } else { // The home button was pushed.
211 // Do not consume the event. The system will process the home command.
212 return super.onOptionsItemSelected(menuItem);
217 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
218 // Run the default commands.
219 super.onSaveInstanceState(savedInstanceState);
221 // Get a handle for the logcat scrollview.
222 ScrollView logcatScrollView = findViewById(R.id.logcat_scrollview);
224 // Get the scrollview Y position.
225 int scrollViewYPositionInt = logcatScrollView.getScrollY();
227 // Store the scrollview Y position in the bundle.
228 savedInstanceState.putInt(SCROLLVIEW_POSITION, scrollViewYPositionInt);
231 // The activity result is called after browsing for a file in the save alert dialog.
233 public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
234 // Run the default commands.
235 super.onActivityResult(requestCode, resultCode, returnedIntent);
237 // Only do something if the user didn't press back from the file picker.
238 if (resultCode == Activity.RESULT_OK) {
239 // Get a handle for the save dialog fragment.
240 DialogFragment saveDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_logcat));
242 // Only update the file name if the dialog still exists.
243 if (saveDialogFragment != null) {
244 // Get a handle for the save dialog.
245 Dialog saveDialog = saveDialogFragment.getDialog();
247 // Remove the lint warning below that the save dialog might be null.
248 assert saveDialog != null;
250 // Get a handle for the file name edit text.
251 EditText fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext);
253 // Get the file name URI from the intent.
254 Uri fileNameUri = returnedIntent.getData();
256 // Get the file name string from the URI.
257 String fileNameString = fileNameUri.toString();
259 // Set the file name text.
260 fileNameEditText.setText(fileNameString);
262 // Move the cursor to the end of the file name edit text.
263 fileNameEditText.setSelection(fileNameString.length());
269 public void onSave(int saveType, DialogFragment dialogFragment) {
270 // Get a handle for the dialog.
271 Dialog dialog = dialogFragment.getDialog();
273 // Remove the lint warning below that the dialog might be null.
274 assert dialog != null;
276 // Get a handle for the file name edit text.
277 EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
279 // Get the file path string.
280 String fileNameString = fileNameEditText.getText().toString();
283 // Get the logcat as a string.
284 String logcatString = logcatTextView.getText().toString();
286 // Create an input stream with the contents of the logcat.
287 InputStream logcatInputStream = new ByteArrayInputStream(logcatString.getBytes(StandardCharsets.UTF_8));
289 // Create a logcat buffered reader.
290 BufferedReader logcatBufferedReader = new BufferedReader(new InputStreamReader(logcatInputStream));
292 // Open an output stream.
293 OutputStream outputStream = getContentResolver().openOutputStream(Uri.parse(fileNameString));
295 // Create a file buffered writer.
296 BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
298 // Create a transfer string.
299 String transferString;
301 // Use the transfer string to copy the logcat from the buffered reader to the buffered writer.
302 while ((transferString = logcatBufferedReader.readLine()) != null) {
303 // Append the line to the buffered writer.
304 bufferedWriter.append(transferString);
306 // Append a line break.
307 bufferedWriter.append("\n");
310 // Flush the buffered writer.
311 bufferedWriter.flush();
313 // Close the inputs and outputs.
314 logcatBufferedReader.close();
315 logcatInputStream.close();
316 bufferedWriter.close();
317 outputStream.close();
319 // Display a snackbar with the saved logcat information.
320 Snackbar.make(logcatTextView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show();
321 } catch (Exception exception) {
322 // Display a snackbar with the error message.
323 Snackbar.make(logcatTextView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show();