Allow saving debuglogs to disk.
This commit is contained in:
parent
999314255c
commit
718eedcb34
5 changed files with 147 additions and 11 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue