mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Fix log submission OOM, improve log scrolling.
We were getting a TransactionTooLargeException when giving an EditText a very large (1.5MB+) text block. This has been resolved by switching to a RecyclerView to show the text line-by-line. As a side-effect, this improves scroll performance on lower-end devices. Also, I added a button to jump to the bottom of the log because I really wanted one :) Fixes #8124
This commit is contained in:
parent
88d94cad92
commit
e1f8e87327
@ -25,42 +25,47 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ScrollView android:id="@+id/log_preview_container"
|
<TextView android:id="@+id/log_submit_confirmation"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="2">
|
android:textSize="15sp"
|
||||||
|
android:text="@string/log_submit_activity__this_log_will_be_posted_online"
|
||||||
|
android:paddingLeft="15dp"
|
||||||
|
android:paddingRight="15dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:background="@color/logsubmit_confirmation_background"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
tools:ignore="UnusedAttribute"/>
|
||||||
|
|
||||||
<LinearLayout android:layout_width="match_parent"
|
<FrameLayout
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:orientation="vertical">
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<TextView android:id="@+id/log_submit_confirmation"
|
<android.support.v7.widget.RecyclerView
|
||||||
android:layout_width="fill_parent"
|
android:id="@+id/log_preview"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:textSize="15sp"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/log_submit_activity__this_log_will_be_posted_online"
|
android:scrollbars="vertical"/>
|
||||||
android:paddingLeft="15dp"
|
|
||||||
android:paddingRight="15dp"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:background="@color/logsubmit_confirmation_background"
|
|
||||||
android:fontFamily="sans-serif-light"
|
|
||||||
tools:ignore="UnusedAttribute"/>
|
|
||||||
|
|
||||||
<EditText android:id="@+id/log_preview"
|
<ImageButton
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/scroll_to_bottom_button"
|
||||||
android:layout_height="wrap_content"
|
android:visibility="visible"
|
||||||
style="?android:attr/textViewStyle"
|
android:layout_width="wrap_content"
|
||||||
android:padding="10dp"
|
android:layout_height="wrap_content"
|
||||||
android:background="@null"
|
android:layout_marginEnd="10dp"
|
||||||
android:fontFamily="monospace"
|
android:layout_marginRight="10dp"
|
||||||
android:hint=""
|
android:layout_marginBottom="10dp"
|
||||||
android:scrollHorizontally="true"
|
android:padding="5dp"
|
||||||
android:inputType="textImeMultiLine|textNoSuggestions|textMultiLine"
|
android:layout_gravity="bottom|end"
|
||||||
android:textSize="10sp"
|
android:background="@drawable/circle_tintable"
|
||||||
tools:ignore="UnusedAttribute,SmallSp"/>
|
android:tint="@color/grey_600"
|
||||||
</LinearLayout>
|
android:elevation="1dp"
|
||||||
</ScrollView>
|
android:alpha="0.9"
|
||||||
|
android:contentDescription="@string/conversation_fragment__scroll_to_the_bottom_content_description"
|
||||||
|
android:src="@drawable/ic_scroll_down"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
|
13
res/layout/item_log_preview.xml
Normal file
13
res/layout/item_log_preview.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<EditText
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
android:typeface="monospace"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:background="@null"
|
||||||
|
android:inputType="textNoSuggestions|textMultiLine"
|
||||||
|
tools:ignore="SmallSp" />
|
@ -6,8 +6,6 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||||
@ -104,53 +102,37 @@ public class ContactNameEditActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
givenName.addTextChangedListener(new SimpleTextWatcher() {
|
givenName.addTextChangedListener(new SimpleTextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
void onTextChanged(String text) {
|
public void onTextChanged(String text) {
|
||||||
viewModel.updateGivenName(text);
|
viewModel.updateGivenName(text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
familyName.addTextChangedListener(new SimpleTextWatcher() {
|
familyName.addTextChangedListener(new SimpleTextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
void onTextChanged(String text) {
|
public void onTextChanged(String text) {
|
||||||
viewModel.updateFamilyName(text);
|
viewModel.updateFamilyName(text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
middleName.addTextChangedListener(new SimpleTextWatcher() {
|
middleName.addTextChangedListener(new SimpleTextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
void onTextChanged(String text) {
|
public void onTextChanged(String text) {
|
||||||
viewModel.updateMiddleName(text);
|
viewModel.updateMiddleName(text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
prefix.addTextChangedListener(new SimpleTextWatcher() {
|
prefix.addTextChangedListener(new SimpleTextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
void onTextChanged(String text) {
|
public void onTextChanged(String text) {
|
||||||
viewModel.updatePrefix(text);
|
viewModel.updatePrefix(text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
suffix.addTextChangedListener(new SimpleTextWatcher() {
|
suffix.addTextChangedListener(new SimpleTextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
void onTextChanged(String text) {
|
public void onTextChanged(String text) {
|
||||||
viewModel.updateSuffix(text);
|
viewModel.updateSuffix(text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static abstract class SimpleTextWatcher implements TextWatcher {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
onTextChanged(s.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) { }
|
|
||||||
|
|
||||||
abstract void onTextChanged(String text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.thoughtcrime.securesms.contactshare;
|
||||||
|
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
|
||||||
|
public abstract class SimpleTextWatcher implements TextWatcher {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
onTextChanged(s.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) { }
|
||||||
|
|
||||||
|
public abstract void onTextChanged(String text);
|
||||||
|
}
|
@ -31,7 +31,10 @@ import android.os.Build;
|
|||||||
import android.os.Build.VERSION;
|
import android.os.Build.VERSION;
|
||||||
import android.os.Build.VERSION_CODES;
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.ClipboardManager;
|
import android.text.ClipboardManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
@ -49,8 +52,10 @@ import org.json.JSONException;
|
|||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.logsubmit.util.Scrubber;
|
import org.thoughtcrime.securesms.logsubmit.util.Scrubber;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@ -82,20 +87,23 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
|
|
||||||
private static final String TAG = SubmitLogFragment.class.getSimpleName();
|
private static final String TAG = SubmitLogFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String API_ENDPOINT = "https://debuglogs.org";
|
||||||
|
|
||||||
private static final String HEADER_SYSINFO = "========== SYSINFO ========";
|
private static final String HEADER_SYSINFO = "========== SYSINFO ========";
|
||||||
private static final String HEADER_LOGCAT = "========== LOGCAT ========";
|
private static final String HEADER_LOGCAT = "========== LOGCAT ========";
|
||||||
private static final String HEADER_LOGGER = "========== LOGGER ========";
|
private static final String HEADER_LOGGER = "========== LOGGER ========";
|
||||||
|
|
||||||
private EditText logPreview;
|
|
||||||
private Button okButton;
|
private Button okButton;
|
||||||
private Button cancelButton;
|
private Button cancelButton;
|
||||||
|
private View scrollButton;
|
||||||
private String supportEmailAddress;
|
private String supportEmailAddress;
|
||||||
private String supportEmailSubject;
|
private String supportEmailSubject;
|
||||||
private String hackSavedLogUrl;
|
private String hackSavedLogUrl;
|
||||||
private boolean emailActivityWasStarted = false;
|
private boolean emailActivityWasStarted = false;
|
||||||
|
|
||||||
private static final String API_ENDPOINT = "https://debuglogs.org";
|
|
||||||
|
|
||||||
|
private RecyclerView logPreview;
|
||||||
|
private LogPreviewAdapter logPreviewAdapter;
|
||||||
private OnLogSubmittedListener mListener;
|
private OnLogSubmittedListener mListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,14 +173,15 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
logPreview = (EditText) getView().findViewById(R.id.log_preview);
|
okButton = getView().findViewById(R.id.ok);
|
||||||
okButton = (Button ) getView().findViewById(R.id.ok );
|
cancelButton = getView().findViewById(R.id.cancel);
|
||||||
cancelButton = (Button ) getView().findViewById(R.id.cancel );
|
logPreview = getView().findViewById(R.id.log_preview);
|
||||||
|
scrollButton = getView().findViewById(R.id.scroll_to_bottom_button);
|
||||||
|
|
||||||
okButton.setOnClickListener(new View.OnClickListener() {
|
okButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
new SubmitToPastebinAsyncTask(logPreview.getText().toString()).execute();
|
new SubmitToPastebinAsyncTask(logPreviewAdapter.getText()).execute();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -182,6 +191,25 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
if (mListener != null) mListener.onCancel();
|
if (mListener != null) mListener.onCancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
scrollButton.setOnClickListener(v -> logPreview.scrollToPosition(logPreviewAdapter.getItemCount() - 1));
|
||||||
|
|
||||||
|
logPreview.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition() < logPreviewAdapter.getItemCount() - 10) {
|
||||||
|
scrollButton.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
scrollButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logPreviewAdapter = new LogPreviewAdapter();
|
||||||
|
|
||||||
|
logPreview.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
logPreview.setAdapter(logPreviewAdapter);
|
||||||
|
|
||||||
new PopulateLogcatAsyncTask(getActivity()).execute();
|
new PopulateLogcatAsyncTask(getActivity()).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,16 +348,30 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
|
|
||||||
String newLogs;
|
String newLogs;
|
||||||
try {
|
try {
|
||||||
newLogs = scrubber.scrub(ApplicationContext.getInstance(context).getPersistentLogger().getLogs().get());
|
long t1 = System.currentTimeMillis();
|
||||||
|
String logs = ApplicationContext.getInstance(context).getPersistentLogger().getLogs().get();
|
||||||
|
Log.i(TAG, "Fetch our logs : " + (System.currentTimeMillis() - t1) + " ms");
|
||||||
|
|
||||||
|
long t2 = System.currentTimeMillis();
|
||||||
|
newLogs = scrubber.scrub(logs);
|
||||||
|
Log.i(TAG, "Scrub our logs: " + (System.currentTimeMillis() - t2) + " ms");
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
Log.w(TAG, "Failed to retrieve new logs.", e);
|
Log.w(TAG, "Failed to retrieve new logs.", e);
|
||||||
newLogs = "Failed to retrieve logs.";
|
newLogs = "Failed to retrieve logs.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long t3 = System.currentTimeMillis();
|
||||||
|
String logcat = grabLogcat();
|
||||||
|
Log.i(TAG, "Fetch logcat: " + (System.currentTimeMillis() - t3) + " ms");
|
||||||
|
|
||||||
|
long t4 = System.currentTimeMillis();
|
||||||
|
String scrubbedLogcat = scrubber.scrub(logcat);
|
||||||
|
Log.i(TAG, "Scrub logcat: " + (System.currentTimeMillis() - t4) + " ms");
|
||||||
|
|
||||||
return HEADER_SYSINFO + "\n\n" +
|
return HEADER_SYSINFO + "\n\n" +
|
||||||
buildDescription(context) + "\n\n\n" +
|
buildDescription(context) + "\n\n\n" +
|
||||||
HEADER_LOGCAT + "\n\n" +
|
HEADER_LOGCAT + "\n\n" +
|
||||||
scrubber.scrub(grabLogcat()) + "\n\n\n" +
|
scrubbedLogcat + "\n\n\n" +
|
||||||
HEADER_LOGGER + "\n\n" +
|
HEADER_LOGGER + "\n\n" +
|
||||||
newLogs;
|
newLogs;
|
||||||
}
|
}
|
||||||
@ -337,7 +379,7 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
logPreview.setText(R.string.log_submit_activity__loading_logs);
|
logPreviewAdapter.setText(getString(R.string.log_submit_activity__loading_logs));
|
||||||
okButton.setEnabled(false);
|
okButton.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +390,7 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
if (mListener != null) mListener.onFailure();
|
if (mListener != null) mListener.onFailure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logPreview.setText(logcat);
|
logPreviewAdapter.setText(logcat);
|
||||||
okButton.setEnabled(true);
|
okButton.setEnabled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -481,4 +523,71 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
public void onFailure();
|
public void onFailure();
|
||||||
public void onCancel();
|
public void onCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class LogPreviewAdapter extends RecyclerView.Adapter<LogPreviewViewHolder> {
|
||||||
|
|
||||||
|
private String[] lines = new String[0];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogPreviewViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
return new LogPreviewViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_log_preview, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(LogPreviewViewHolder holder, int position) {
|
||||||
|
holder.bind(lines, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewRecycled(LogPreviewViewHolder holder) {
|
||||||
|
holder.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return lines.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setText(@NonNull String text) {
|
||||||
|
lines = text.split("\n");
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getText() {
|
||||||
|
return Util.join(lines, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class LogPreviewViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private EditText text;
|
||||||
|
private String[] lines;
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
LogPreviewViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
text = (EditText) itemView;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(String[] lines, int index) {
|
||||||
|
this.lines = lines;
|
||||||
|
this.index = index;
|
||||||
|
|
||||||
|
text.setText(lines[index]);
|
||||||
|
text.addTextChangedListener(textWatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unbind() {
|
||||||
|
text.removeTextChangedListener(textWatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SimpleTextWatcher textWatcher = new SimpleTextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(String text) {
|
||||||
|
if (lines != null) {
|
||||||
|
lines[index] = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user