From 718eedcb347e5a993940d829283edf96eb6bcbc2 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 5 Aug 2022 13:15:39 -0400 Subject: [PATCH] Allow saving debuglogs to disk. --- .../logsubmit/SubmitDebugLogActivity.java | 48 ++++++++++++++ .../logsubmit/SubmitDebugLogRepository.java | 64 +++++++++++++++---- .../logsubmit/SubmitDebugLogViewModel.java | 32 ++++++++++ .../main/res/menu/submit_debug_log_normal.xml | 8 +++ app/src/main/res/values/strings.xml | 6 ++ 5 files changed, 147 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java index 95364f9def..60b7e03325 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java @@ -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 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; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogRepository.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogRepository.java index 9d85cf94c1..aadddb7b62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogRepository.java @@ -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 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 submitLogInternal(long untilTime, @NonNull List 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 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 { void onResult(E result); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java index 832afe166f..1eff722c3f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java @@ -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; private final ProxyPagingController pagingController; private final List staticLines; private final MediatorLiveData> lines; + private final SingleLiveEvent 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 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 create(@NonNull Class modelClass) { diff --git a/app/src/main/res/menu/submit_debug_log_normal.xml b/app/src/main/res/menu/submit_debug_log_normal.xml index 2f7e2f4dac..1007f2b7d7 100644 --- a/app/src/main/res/menu/submit_debug_log_normal.xml +++ b/app/src/main/res/menu/submit_debug_log_normal.xml @@ -22,4 +22,12 @@ android:title="@string/SubmitDebugLogActivity_done" android:visible="false" app:showAsAction="always" /> + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae7ca0a858..4a7e57a452 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1769,6 +1769,12 @@ Edit Done + + Save + + Failed to save + + Save complete Tap a line to delete it Submit Failed to submit logs