kopia lustrzana https://github.com/ryukoposting/Signal-Android
Allow saving debuglogs to disk.
rodzic
999314255c
commit
718eedcb34
|
@ -1,6 +1,10 @@
|
||||||
package org.thoughtcrime.securesms.logsubmit;
|
package org.thoughtcrime.securesms.logsubmit;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
|
@ -12,6 +16,7 @@ import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.core.app.ShareCompat;
|
import androidx.core.app.ShareCompat;
|
||||||
|
@ -34,6 +39,8 @@ import java.util.List;
|
||||||
|
|
||||||
public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugLogAdapter.Listener {
|
public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugLogAdapter.Listener {
|
||||||
|
|
||||||
|
private static final int CODE_SAVE = 24601;
|
||||||
|
|
||||||
private RecyclerView lineList;
|
private RecyclerView lineList;
|
||||||
private SubmitDebugLogAdapter adapter;
|
private SubmitDebugLogAdapter adapter;
|
||||||
private SubmitDebugLogViewModel viewModel;
|
private SubmitDebugLogViewModel viewModel;
|
||||||
|
@ -48,6 +55,9 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
|
||||||
private MenuItem editMenuItem;
|
private MenuItem editMenuItem;
|
||||||
private MenuItem doneMenuItem;
|
private MenuItem doneMenuItem;
|
||||||
private MenuItem searchMenuItem;
|
private MenuItem searchMenuItem;
|
||||||
|
private MenuItem saveMenuItem;
|
||||||
|
|
||||||
|
private AlertDialog fileProgressDialog;
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
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.editMenuItem = menu.findItem(R.id.menu_edit_log);
|
||||||
this.doneMenuItem = menu.findItem(R.id.menu_done_editing_log);
|
this.doneMenuItem = menu.findItem(R.id.menu_done_editing_log);
|
||||||
this.searchMenuItem = menu.findItem(R.id.menu_search);
|
this.searchMenuItem = menu.findItem(R.id.menu_search);
|
||||||
|
this.saveMenuItem = menu.findItem(R.id.menu_save);
|
||||||
|
|
||||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||||
SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() {
|
SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() {
|
||||||
|
@ -123,6 +134,13 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
|
||||||
viewModel.onEditButtonPressed();
|
viewModel.onEditButtonPressed();
|
||||||
} else if (item.getItemId() == R.id.menu_done_editing_log) {
|
} else if (item.getItemId() == R.id.menu_done_editing_log) {
|
||||||
viewModel.onDoneEditingButtonPressed();
|
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;
|
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
|
@Override
|
||||||
public void onLogDeleted(@NonNull LogLine logLine) {
|
public void onLogDeleted(@NonNull LogLine logLine) {
|
||||||
viewModel.onLogDeleted(logLine);
|
viewModel.onLogDeleted(logLine);
|
||||||
|
@ -182,6 +211,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
|
||||||
private void initViewModel() {
|
private void initViewModel() {
|
||||||
viewModel.getLines().observe(this, this::presentLines);
|
viewModel.getLines().observe(this, this::presentLines);
|
||||||
viewModel.getMode().observe(this, this::presentMode);
|
viewModel.getMode().observe(this, this::presentMode);
|
||||||
|
viewModel.getEvents().observe(this, this::presentEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void presentLines(@NonNull List<LogLine> lines) {
|
private void presentLines(@NonNull List<LogLine> lines) {
|
||||||
|
@ -201,6 +231,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
|
||||||
case NORMAL:
|
case NORMAL:
|
||||||
editBanner.setVisibility(View.GONE);
|
editBanner.setVisibility(View.GONE);
|
||||||
adapter.setEditing(false);
|
adapter.setEditing(false);
|
||||||
|
saveMenuItem.setVisible(true);
|
||||||
// TODO [greyson][log] Not yet implemented
|
// TODO [greyson][log] Not yet implemented
|
||||||
// editMenuItem.setVisible(true);
|
// editMenuItem.setVisible(true);
|
||||||
// doneMenuItem.setVisible(false);
|
// doneMenuItem.setVisible(false);
|
||||||
|
@ -212,6 +243,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
|
||||||
editMenuItem.setVisible(false);
|
editMenuItem.setVisible(false);
|
||||||
doneMenuItem.setVisible(false);
|
doneMenuItem.setVisible(false);
|
||||||
searchMenuItem.setVisible(false);
|
searchMenuItem.setVisible(false);
|
||||||
|
saveMenuItem.setVisible(false);
|
||||||
break;
|
break;
|
||||||
case EDIT:
|
case EDIT:
|
||||||
editBanner.setVisibility(View.VISIBLE);
|
editBanner.setVisibility(View.VISIBLE);
|
||||||
|
@ -219,6 +251,22 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
|
||||||
editMenuItem.setVisible(false);
|
editMenuItem.setVisible(false);
|
||||||
doneMenuItem.setVisible(true);
|
doneMenuItem.setVisible(true);
|
||||||
searchMenuItem.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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ import org.thoughtcrime.securesms.util.Stopwatch;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -36,6 +38,8 @@ import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
|
@ -126,6 +130,38 @@ public class SubmitDebugLogRepository {
|
||||||
SignalExecutors.UNBOUNDED.execute(() -> callback.onResult(submitLogInternal(untilTime, prefixLines, trace)));
|
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
|
@WorkerThread
|
||||||
private @NonNull Optional<String> submitLogInternal(long untilTime, @NonNull List<LogLine> prefixLines, @Nullable byte[] trace) {
|
private @NonNull Optional<String> submitLogInternal(long untilTime, @NonNull List<LogLine> prefixLines, @Nullable byte[] trace) {
|
||||||
String traceUrl = null;
|
String traceUrl = null;
|
||||||
|
@ -138,17 +174,7 @@ public class SubmitDebugLogRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder prefixStringBuilder = new StringBuilder();
|
StringBuilder prefixStringBuilder = linesToStringBuilder(prefixLines, traceUrl);
|
||||||
for (LogLine line : prefixLines) {
|
|
||||||
switch (line.getPlaceholderType()) {
|
|
||||||
case NONE:
|
|
||||||
prefixStringBuilder.append(line.getText()).append('\n');
|
|
||||||
break;
|
|
||||||
case TRACE:
|
|
||||||
prefixStringBuilder.append(traceUrl).append('\n');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Stopwatch stopwatch = new Stopwatch("log-upload");
|
Stopwatch stopwatch = new Stopwatch("log-upload");
|
||||||
|
@ -322,6 +348,22 @@ public class SubmitDebugLogRepository {
|
||||||
return out.toString();
|
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> {
|
public interface Callback<E> {
|
||||||
void onResult(E result);
|
void onResult(E result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.thoughtcrime.securesms.logsubmit;
|
package org.thoughtcrime.securesms.logsubmit;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MediatorLiveData;
|
import androidx.lifecycle.MediatorLiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
@ -17,6 +20,7 @@ import org.signal.paging.PagingController;
|
||||||
import org.signal.paging.ProxyPagingController;
|
import org.signal.paging.ProxyPagingController;
|
||||||
import org.thoughtcrime.securesms.database.LogDatabase;
|
import org.thoughtcrime.securesms.database.LogDatabase;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -24,11 +28,14 @@ import java.util.Optional;
|
||||||
|
|
||||||
public class SubmitDebugLogViewModel extends ViewModel {
|
public class SubmitDebugLogViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(SubmitDebugLogViewModel.class);
|
||||||
|
|
||||||
private final SubmitDebugLogRepository repo;
|
private final SubmitDebugLogRepository repo;
|
||||||
private final MutableLiveData<Mode> mode;
|
private final MutableLiveData<Mode> mode;
|
||||||
private final ProxyPagingController<Long> pagingController;
|
private final ProxyPagingController<Long> pagingController;
|
||||||
private final List<LogLine> staticLines;
|
private final List<LogLine> staticLines;
|
||||||
private final MediatorLiveData<List<LogLine>> lines;
|
private final MediatorLiveData<List<LogLine>> lines;
|
||||||
|
private final SingleLiveEvent<Event> event;
|
||||||
private final long firstViewTime;
|
private final long firstViewTime;
|
||||||
private final byte[] trace;
|
private final byte[] trace;
|
||||||
|
|
||||||
|
@ -41,6 +48,7 @@ public class SubmitDebugLogViewModel extends ViewModel {
|
||||||
this.firstViewTime = System.currentTimeMillis();
|
this.firstViewTime = System.currentTimeMillis();
|
||||||
this.staticLines = new ArrayList<>();
|
this.staticLines = new ArrayList<>();
|
||||||
this.lines = new MediatorLiveData<>();
|
this.lines = new MediatorLiveData<>();
|
||||||
|
this.event = new SingleLiveEvent<>();
|
||||||
|
|
||||||
repo.getPrefixLogLines(staticLines -> {
|
repo.getPrefixLogLines(staticLines -> {
|
||||||
this.staticLines.addAll(staticLines);
|
this.staticLines.addAll(staticLines);
|
||||||
|
@ -89,6 +97,26 @@ public class SubmitDebugLogViewModel extends ViewModel {
|
||||||
return result;
|
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) {
|
void onQueryUpdated(@NonNull String query) {
|
||||||
throw new UnsupportedOperationException("Not yet implemented.");
|
throw new UnsupportedOperationException("Not yet implemented.");
|
||||||
}
|
}
|
||||||
|
@ -122,6 +150,10 @@ public class SubmitDebugLogViewModel extends ViewModel {
|
||||||
NORMAL, EDIT, SUBMITTING
|
NORMAL, EDIT, SUBMITTING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Event {
|
||||||
|
FILE_SAVE_SUCCESS, FILE_SAVE_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
public static class Factory extends ViewModelProvider.NewInstanceFactory {
|
public static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||||
@Override
|
@Override
|
||||||
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||||
|
|
|
@ -22,4 +22,12 @@
|
||||||
android:title="@string/SubmitDebugLogActivity_done"
|
android:title="@string/SubmitDebugLogActivity_done"
|
||||||
android:visible="false"
|
android:visible="false"
|
||||||
app:showAsAction="always" />
|
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>
|
</menu>
|
|
@ -1769,6 +1769,12 @@
|
||||||
<!-- SubmitDebugLogActivity -->
|
<!-- SubmitDebugLogActivity -->
|
||||||
<string name="SubmitDebugLogActivity_edit">Edit</string>
|
<string name="SubmitDebugLogActivity_edit">Edit</string>
|
||||||
<string name="SubmitDebugLogActivity_done">Done</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_tap_a_line_to_delete_it">Tap a line to delete it</string>
|
||||||
<string name="SubmitDebugLogActivity_submit">Submit</string>
|
<string name="SubmitDebugLogActivity_submit">Submit</string>
|
||||||
<string name="SubmitDebugLogActivity_failed_to_submit_logs">Failed to submit logs</string>
|
<string name="SubmitDebugLogActivity_failed_to_submit_logs">Failed to submit logs</string>
|
||||||
|
|
Ładowanie…
Reference in New Issue