Allow saving debuglogs to disk.

This commit is contained in:
Greyson Parrelli 2022-08-05 13:15:39 -04:00
parent 999314255c
commit 718eedcb34
5 changed files with 147 additions and 11 deletions

View file

@ -1,6 +1,10 @@
package org.thoughtcrime.securesms.logsubmit;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.URLSpan;
@ -12,6 +16,7 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.core.app.ShareCompat;
@ -34,6 +39,8 @@ import java.util.List;
public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugLogAdapter.Listener {
private static final int CODE_SAVE = 24601;
private RecyclerView lineList;
private SubmitDebugLogAdapter adapter;
private SubmitDebugLogViewModel viewModel;
@ -48,6 +55,9 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
private MenuItem editMenuItem;
private MenuItem doneMenuItem;
private MenuItem searchMenuItem;
private MenuItem saveMenuItem;
private AlertDialog fileProgressDialog;
private final DynamicTheme dynamicTheme = new DynamicTheme();
@ -78,6 +88,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
this.editMenuItem = menu.findItem(R.id.menu_edit_log);
this.doneMenuItem = menu.findItem(R.id.menu_done_editing_log);
this.searchMenuItem = menu.findItem(R.id.menu_search);
this.saveMenuItem = menu.findItem(R.id.menu_save);
SearchView searchView = (SearchView) searchMenuItem.getActionView();
SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() {
@ -123,6 +134,13 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
viewModel.onEditButtonPressed();
} else if (item.getItemId() == R.id.menu_done_editing_log) {
viewModel.onDoneEditingButtonPressed();
} else if (item.getItemId() == R.id.menu_save) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/zip");
intent.putExtra(Intent.EXTRA_TITLE, "signal-log-" + System.currentTimeMillis() + ".zip");
startActivityForResult(intent, CODE_SAVE);
}
return false;
@ -135,6 +153,17 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CODE_SAVE && resultCode == Activity.RESULT_OK) {
Uri uri = data != null ? data.getData() : null;
viewModel.onDiskSaveLocationReady(uri);
fileProgressDialog = SimpleProgressDialog.show(this);
}
}
@Override
public void onLogDeleted(@NonNull LogLine logLine) {
viewModel.onLogDeleted(logLine);
@ -182,6 +211,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
private void initViewModel() {
viewModel.getLines().observe(this, this::presentLines);
viewModel.getMode().observe(this, this::presentMode);
viewModel.getEvents().observe(this, this::presentEvents);
}
private void presentLines(@NonNull List<LogLine> lines) {
@ -201,6 +231,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
case NORMAL:
editBanner.setVisibility(View.GONE);
adapter.setEditing(false);
saveMenuItem.setVisible(true);
// TODO [greyson][log] Not yet implemented
// editMenuItem.setVisible(true);
// doneMenuItem.setVisible(false);
@ -212,6 +243,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
editMenuItem.setVisible(false);
doneMenuItem.setVisible(false);
searchMenuItem.setVisible(false);
saveMenuItem.setVisible(false);
break;
case EDIT:
editBanner.setVisibility(View.VISIBLE);
@ -219,6 +251,22 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
editMenuItem.setVisible(false);
doneMenuItem.setVisible(true);
searchMenuItem.setVisible(true);
saveMenuItem.setVisible(false);
break;
}
}
private void presentEvents(@NonNull SubmitDebugLogViewModel.Event event) {
switch (event) {
case FILE_SAVE_SUCCESS:
Toast.makeText(this, R.string.SubmitDebugLogActivity_save_complete, Toast.LENGTH_SHORT).show();
if (fileProgressDialog != null) {
fileProgressDialog.dismiss();
fileProgressDialog = null;
}
break;
case FILE_SAVE_ERROR:
Toast.makeText(this, R.string.SubmitDebugLogActivity_failed_to_save, Toast.LENGTH_SHORT).show();
break;
}
}

View file

@ -29,6 +29,8 @@ import org.thoughtcrime.securesms.util.Stopwatch;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -36,6 +38,8 @@ import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
@ -126,6 +130,38 @@ public class SubmitDebugLogRepository {
SignalExecutors.UNBOUNDED.execute(() -> callback.onResult(submitLogInternal(untilTime, prefixLines, trace)));
}
public void writeLogToDisk(@NonNull Uri uri, long untilTime, Callback<Boolean> callback) {
SignalExecutors.UNBOUNDED.execute(() -> {
try (ZipOutputStream outputStream = new ZipOutputStream(context.getContentResolver().openOutputStream(uri))) {
StringBuilder prefixLines = linesToStringBuilder(getPrefixLogLinesInternal(), null);
outputStream.putNextEntry(new ZipEntry("log.txt"));
outputStream.write(prefixLines.toString().getBytes(StandardCharsets.UTF_8));
try (LogDatabase.Reader reader = LogDatabase.getInstance(context).getAllBeforeTime(untilTime)) {
while (reader.hasNext()) {
outputStream.write(reader.next().getBytes());
outputStream.write("\n".getBytes());
}
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to read row!", e);
callback.onResult(false);
return;
}
outputStream.closeEntry();
outputStream.putNextEntry(new ZipEntry("signal.trace"));
outputStream.write(Tracer.getInstance().serialize());
outputStream.closeEntry();
callback.onResult(true);
} catch (IOException e) {
callback.onResult(false);
}
});
}
@WorkerThread
private @NonNull Optional<String> submitLogInternal(long untilTime, @NonNull List<LogLine> prefixLines, @Nullable byte[] trace) {
String traceUrl = null;
@ -138,17 +174,7 @@ public class SubmitDebugLogRepository {
}
}
StringBuilder prefixStringBuilder = new StringBuilder();
for (LogLine line : prefixLines) {
switch (line.getPlaceholderType()) {
case NONE:
prefixStringBuilder.append(line.getText()).append('\n');
break;
case TRACE:
prefixStringBuilder.append(traceUrl).append('\n');
break;
}
}
StringBuilder prefixStringBuilder = linesToStringBuilder(prefixLines, traceUrl);
try {
Stopwatch stopwatch = new Stopwatch("log-upload");
@ -322,6 +348,22 @@ public class SubmitDebugLogRepository {
return out.toString();
}
private static @NonNull StringBuilder linesToStringBuilder(@NonNull List<LogLine> lines, @Nullable String traceUrl) {
StringBuilder stringBuilder = new StringBuilder();
for (LogLine line : lines) {
switch (line.getPlaceholderType()) {
case NONE:
stringBuilder.append(line.getText()).append('\n');
break;
case TRACE:
stringBuilder.append(traceUrl).append('\n');
break;
}
}
return stringBuilder;
}
public interface Callback<E> {
void onResult(E result);
}

View file

@ -1,6 +1,9 @@
package org.thoughtcrime.securesms.logsubmit;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
@ -17,6 +20,7 @@ import org.signal.paging.PagingController;
import org.signal.paging.ProxyPagingController;
import org.thoughtcrime.securesms.database.LogDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import java.util.ArrayList;
import java.util.List;
@ -24,11 +28,14 @@ import java.util.Optional;
public class SubmitDebugLogViewModel extends ViewModel {
private static final String TAG = Log.tag(SubmitDebugLogViewModel.class);
private final SubmitDebugLogRepository repo;
private final MutableLiveData<Mode> mode;
private final ProxyPagingController<Long> pagingController;
private final List<LogLine> staticLines;
private final MediatorLiveData<List<LogLine>> lines;
private final SingleLiveEvent<Event> event;
private final long firstViewTime;
private final byte[] trace;
@ -41,6 +48,7 @@ public class SubmitDebugLogViewModel extends ViewModel {
this.firstViewTime = System.currentTimeMillis();
this.staticLines = new ArrayList<>();
this.lines = new MediatorLiveData<>();
this.event = new SingleLiveEvent<>();
repo.getPrefixLogLines(staticLines -> {
this.staticLines.addAll(staticLines);
@ -89,6 +97,26 @@ public class SubmitDebugLogViewModel extends ViewModel {
return result;
}
@NonNull LiveData<Event> getEvents() {
return event;
}
void onDiskSaveLocationReady(@Nullable Uri uri) {
if (uri == null) {
Log.w(TAG, "Null URI!");
event.postValue(Event.FILE_SAVE_ERROR);
return;
}
repo.writeLogToDisk(uri, firstViewTime, success -> {
if (success) {
event.postValue(Event.FILE_SAVE_SUCCESS);
} else {
event.postValue(Event.FILE_SAVE_ERROR);
}
});
}
void onQueryUpdated(@NonNull String query) {
throw new UnsupportedOperationException("Not yet implemented.");
}
@ -122,6 +150,10 @@ public class SubmitDebugLogViewModel extends ViewModel {
NORMAL, EDIT, SUBMITTING
}
enum Event {
FILE_SAVE_SUCCESS, FILE_SAVE_ERROR
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
@Override
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {

View file

@ -22,4 +22,12 @@
android:title="@string/SubmitDebugLogActivity_done"
android:visible="false"
app:showAsAction="always" />
<item
android:id="@+id/menu_save"
android:icon="@drawable/ic_save_24"
android:title="@string/SubmitDebugLogActivity_save"
android:visible="false"
app:showAsAction="never" />
</menu>

View file

@ -1769,6 +1769,12 @@
<!-- SubmitDebugLogActivity -->
<string name="SubmitDebugLogActivity_edit">Edit</string>
<string name="SubmitDebugLogActivity_done">Done</string>
<!-- Menu option to save a debug log file to disk. -->
<string name="SubmitDebugLogActivity_save">Save</string>
<!-- Error that is show in a toast when we fail to save a debug log file to disk. -->
<string name="SubmitDebugLogActivity_failed_to_save">Failed to save</string>
<!-- Toast that is show to notify that we have saved the debug log file to disk. -->
<string name="SubmitDebugLogActivity_save_complete">Save complete</string>
<string name="SubmitDebugLogActivity_tap_a_line_to_delete_it">Tap a line to delete it</string>
<string name="SubmitDebugLogActivity_submit">Submit</string>
<string name="SubmitDebugLogActivity_failed_to_submit_logs">Failed to submit logs</string>