Fix a crash when opening a drawer while restarting. https://redmine.stoutner.com...
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / activities / LogcatActivity.java
1 /*
2  * Copyright © 2019-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.activities;
21
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;
39
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;
46
47 import com.google.android.material.snackbar.Snackbar;
48
49 import com.stoutner.privacybrowser.R;
50 import com.stoutner.privacybrowser.asynctasks.GetLogcat;
51 import com.stoutner.privacybrowser.dialogs.SaveDialog;
52
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;
62
63 public class LogcatActivity extends AppCompatActivity implements SaveDialog.SaveListener {
64     // Declare the class constants.
65     private final String SCROLLVIEW_POSITION = "scrollview_position";
66
67     // Define the class views.
68     private TextView logcatTextView;
69
70     @Override
71     public void onCreate(Bundle savedInstanceState) {
72         // Get a handle for the shared preferences.
73         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
74
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);
78
79         // Disable screenshots if not allowed.
80         if (!allowScreenshots) {
81             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
82         }
83
84         // Set the theme.
85         setTheme(R.style.PrivacyBrowser);
86
87         // Run the default commands.
88         super.onCreate(savedInstanceState);
89
90         // Set the content view.
91         if (bottomAppBar) {
92             setContentView(R.layout.logcat_bottom_appbar);
93         } else {
94             setContentView(R.layout.logcat_top_appbar);
95         }
96
97         // Get handles for the views.
98         Toolbar toolbar = findViewById(R.id.logcat_toolbar);
99         SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.logcat_swiperefreshlayout);
100
101         // Set the toolbar as the action bar.
102         setSupportActionBar(toolbar);
103
104         // Get a handle for the action bar.
105         ActionBar actionBar = getSupportActionBar();
106
107         // Remove the incorrect lint warning that the action bar might be null.
108         assert actionBar != null;
109
110         // Display the the back arrow in the action bar.
111         actionBar.setDisplayHomeAsUpEnabled(true);
112
113         // Populate the class views.
114         logcatTextView = findViewById(R.id.logcat_textview);
115
116         // Implement swipe to refresh.
117         swipeRefreshLayout.setOnRefreshListener(() -> {
118             // Get the current logcat.
119             new GetLogcat(this, 0).execute();
120         });
121
122         // Get the current theme status.
123         int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
124
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);
128         } else {
129             swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
130         }
131
132         // Initialize a color background typed value.
133         TypedValue colorBackgroundTypedValue = new TypedValue();
134
135         // Get the color background from the theme.
136         getTheme().resolveAttribute(android.R.attr.colorBackground, colorBackgroundTypedValue, true);
137
138         // Get the color background int from the typed value.
139         int colorBackgroundInt = colorBackgroundTypedValue.data;
140
141         // Set the swipe refresh background color.
142         swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt);
143
144         // Initialize the scrollview Y position int.
145         int scrollViewYPositionInt = 0;
146
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);
151         }
152
153         // Get the logcat.
154         new GetLogcat(this, scrollViewYPositionInt).execute();
155     }
156
157     @Override
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);
161
162         // Display the menu.
163         return true;
164     }
165
166     @Override
167     public boolean onOptionsItemSelected(MenuItem menuItem) {
168         // Get the selected menu item ID.
169         int menuItemId = menuItem.getItemId();
170
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);
175
176             // Remove the incorrect lint error below that the clipboard manager might be null.
177             assert clipboardManager != null;
178
179             // Save the logcat in a clip data.
180             ClipData logcatClipData = ClipData.newPlainText(getString(R.string.logcat), logcatTextView.getText());
181
182             // Place the clip data on the clipboard.
183             clipboardManager.setPrimaryClip(logcatClipData);
184
185             // Display a snackbar.
186             Snackbar.make(logcatTextView, R.string.logcat_copied, Snackbar.LENGTH_SHORT).show();
187
188             // Consume the event.
189             return true;
190         } else if (menuItemId == R.id.save) {  // Save was selected.
191             // Instantiate the save alert dialog.
192             DialogFragment saveDialogFragment = SaveDialog.save(SaveDialog.SAVE_LOGCAT);
193
194             // Show the save alert dialog.
195             saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_logcat));
196
197             // Consume the event.
198             return true;
199         } else if (menuItemId == R.id.clear) {  // Clear was selected.
200             try {
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");
203
204                 // Wait for the process to finish.
205                 process.waitFor();
206
207                 // Reload the logcat.
208                 new GetLogcat(this, 0).execute();
209             } catch (IOException | InterruptedException exception) {
210                 // Do nothing.
211             }
212
213             // Consume the event.
214             return true;
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);
218         }
219     }
220
221     @Override
222     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
223         // Run the default commands.
224         super.onSaveInstanceState(savedInstanceState);
225
226         // Get a handle for the logcat scrollview.
227         ScrollView logcatScrollView = findViewById(R.id.logcat_scrollview);
228
229         // Get the scrollview Y position.
230         int scrollViewYPositionInt = logcatScrollView.getScrollY();
231
232         // Store the scrollview Y position in the bundle.
233         savedInstanceState.putInt(SCROLLVIEW_POSITION, scrollViewYPositionInt);
234     }
235
236     // The activity result is called after browsing for a file in the save alert dialog.
237     @Override
238     public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
239         // Run the default commands.
240         super.onActivityResult(requestCode, resultCode, returnedIntent);
241
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));
246
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();
251
252                 // Remove the lint warning below that the save dialog might be null.
253                 assert saveDialog != null;
254
255                 // Get a handle for the file name edit text.
256                 EditText fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext);
257
258                 // Get the file name URI from the intent.
259                 Uri fileNameUri = returnedIntent.getData();
260
261                 // Get the file name string from the URI.
262                 String fileNameString = fileNameUri.toString();
263
264                 // Set the file name text.
265                 fileNameEditText.setText(fileNameString);
266
267                 // Move the cursor to the end of the file name edit text.
268                 fileNameEditText.setSelection(fileNameString.length());
269             }
270         }
271     }
272
273     @Override
274     public void onSave(int saveType, DialogFragment dialogFragment) {
275         // Get a handle for the dialog.
276         Dialog dialog = dialogFragment.getDialog();
277
278         // Remove the lint warning below that the dialog might be null.
279         assert dialog != null;
280
281         // Get a handle for the file name edit text.
282         EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
283
284         // Get the file path string.
285         String fileNameString = fileNameEditText.getText().toString();
286
287         try {
288             // Get the logcat as a string.
289             String logcatString = logcatTextView.getText().toString();
290
291             // Create an input stream with the contents of the logcat.
292             InputStream logcatInputStream = new ByteArrayInputStream(logcatString.getBytes(StandardCharsets.UTF_8));
293
294             // Create a logcat buffered reader.
295             BufferedReader logcatBufferedReader = new BufferedReader(new InputStreamReader(logcatInputStream));
296
297             // Open an output stream.
298             OutputStream outputStream = getContentResolver().openOutputStream(Uri.parse(fileNameString));
299
300             // Create a file buffered writer.
301             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
302
303             // Create a transfer string.
304             String transferString;
305
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);
310
311                 // Append a line break.
312                 bufferedWriter.append("\n");
313             }
314
315             // Flush the buffered writer.
316             bufferedWriter.flush();
317
318             // Close the inputs and outputs.
319             logcatBufferedReader.close();
320             logcatInputStream.close();
321             bufferedWriter.close();
322             outputStream.close();
323
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();
329         }
330     }
331 }