From 24360a41ffc85f2a1de57ec58fc2b65433955e99 Mon Sep 17 00:00:00 2001 From: lisa-signal Date: Fri, 11 Jul 2025 15:01:47 -0400 Subject: [PATCH] Change debug log from RecyclerView to WebView. --- .../logsubmit/SubmitDebugLogActivity.java | 160 +++++++++++---- .../logsubmit/SubmitDebugLogAdapter.java | 188 ------------------ .../logsubmit/SubmitDebugLogViewModel.java | 24 +-- .../res/layout/submit_debug_log_activity.xml | 2 +- 4 files changed, 131 insertions(+), 243 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogAdapter.java 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 f41aa1c6f6..c8bb64582f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java @@ -11,6 +11,8 @@ import android.text.util.Linkify; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.webkit.WebView; +import android.webkit.WebViewClient; import android.widget.TextView; import android.widget.Toast; @@ -19,13 +21,13 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; import androidx.core.app.ShareCompat; +import androidx.core.content.ContextCompat; import androidx.core.text.util.LinkifyCompat; import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import org.json.JSONObject; import org.thoughtcrime.securesms.BaseActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.ProgressCard; @@ -38,13 +40,13 @@ import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton; import java.util.List; -public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugLogAdapter.Listener { +public class SubmitDebugLogActivity extends BaseActivity { private static final int CODE_SAVE = 24601; - private RecyclerView lineList; - private SubmitDebugLogAdapter adapter; + private WebView logWebView; private SubmitDebugLogViewModel viewModel; + private boolean isPageLoaded; private View warningBanner; private View editBanner; @@ -165,13 +167,81 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL } } - @Override - public void onLogDeleted(@NonNull LogLine logLine) { - viewModel.onLogDeleted(logLine); + // TODO [lisa][debug-log-delete] +// public void onLogDeleted(@NonNull LogLine logLine) { +// viewModel.onLogDeleted(logLine); +// } + + private void initWebView() { + StringBuilder body = new StringBuilder(); + + int backgroundColor = ContextCompat.getColor(this, R.color.signal_colorBackground); + int noneColor = ContextCompat.getColor(this, R.color.debuglog_color_none); + int verboseColor = ContextCompat.getColor(this, R.color.debuglog_color_verbose); + int debugColor = ContextCompat.getColor(this, R.color.debuglog_color_debug); + int infoColor = ContextCompat.getColor(this, R.color.debuglog_color_info); + int warningColor = ContextCompat.getColor(this, R.color.debuglog_color_warn); + int errorColor = ContextCompat.getColor(this, R.color.debuglog_color_error); + + String css = String.format(""" + + """, + intToCssHex(backgroundColor), + intToCssHex(noneColor), + intToCssHex(verboseColor), + intToCssHex(debugColor), + intToCssHex(infoColor), + intToCssHex(warningColor), + intToCssHex(errorColor) + ); + + String js = """ + + """; + + body.append(String.format("%s%s
", css, js)); + + String htmlContent = body.toString(); + + logWebView.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null); + + logWebView.getSettings().setBuiltInZoomControls(true); + logWebView.getSettings().setDisplayZoomControls(false); + logWebView.getSettings().setUseWideViewPort(true); + logWebView.getSettings().setJavaScriptEnabled(true); + logWebView.setHorizontalScrollBarEnabled(true); + + logWebView.setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(WebView view, String url) { + isPageLoaded = true; + } + }); + } + + private String intToCssHex(int color) { + return String.format("#%06X", 0xFFFFFF & color); } private void initView() { - this.lineList = findViewById(R.id.debug_log_lines); + this.logWebView = findViewById(R.id.debug_log_lines); this.warningBanner = findViewById(R.id.debug_log_warning_banner); this.editBanner = findViewById(R.id.debug_log_edit_banner); this.submitButton = findViewById(R.id.debug_log_submit_button); @@ -179,35 +249,27 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL this.scrollToTopButton = findViewById(R.id.debug_log_scroll_to_top); this.progressCard = findViewById(R.id.debug_log_progress_card); - this.adapter = new SubmitDebugLogAdapter(this, viewModel.getPagingController()); - - this.lineList.setLayoutManager(new LinearLayoutManager(this)); - this.lineList.setAdapter(adapter); - this.lineList.setItemAnimator(null); + initWebView(); submitButton.setOnClickListener(v -> onSubmitClicked()); - scrollToBottomButton.setOnClickListener(v -> lineList.scrollToPosition(adapter.getItemCount() - 1)); - scrollToTopButton.setOnClickListener(v -> lineList.scrollToPosition(0)); + scrollToBottomButton.setOnClickListener(v -> logWebView.pageDown(true)); + scrollToTopButton.setOnClickListener(v -> logWebView.pageUp(true)); - lineList.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - if (((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition() < adapter.getItemCount() - 10) { - scrollToBottomButton.setVisibility(View.VISIBLE); - } else { - scrollToBottomButton.setVisibility(View.GONE); - } + logWebView.getViewTreeObserver().addOnScrollChangedListener(() -> { + if (logWebView.getScrollY() + logWebView.getHeight() < logWebView.getContentHeight() * logWebView.getScale() - 10) { + scrollToBottomButton.setVisibility(View.VISIBLE); + } else { + scrollToBottomButton.setVisibility(View.GONE); + } - if (((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition() > 10) { - scrollToTopButton.setVisibility(View.VISIBLE); - } else { - scrollToTopButton.setVisibility(View.GONE); - } + if (logWebView.getScrollY() > 10) { + scrollToTopButton.setVisibility(View.VISIBLE); + } else { + scrollToTopButton.setVisibility(View.GONE); } }); this.progressCard.setVisibility(View.VISIBLE); - } private void initViewModel() { @@ -224,14 +286,44 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL submitButton.setVisibility(View.VISIBLE); } - adapter.submitList(lines); + if (!isPageLoaded) { + initWebView(); + } + + StringBuilder appendScript = new StringBuilder(); + + for (LogLine line : lines) { + if (line == null) continue; + + String newLine = line.getText(); + String lineClass = switch (line.getStyle()) { + case VERBOSE -> "verbose"; + case DEBUG -> "debug"; + case INFO -> "info"; + case WARNING -> "warning"; + case ERROR -> "error"; + default -> "none"; + }; + + String script = String.format( + "appendLine(%s, %s);\n", + JSONObject.quote(newLine), + JSONObject.quote(lineClass) + ); + + appendScript.append(script); + } + + String scriptContent = appendScript.toString(); + logWebView.evaluateJavascript(scriptContent, null); } private void presentMode(@NonNull SubmitDebugLogViewModel.Mode mode) { switch (mode) { case NORMAL: editBanner.setVisibility(View.GONE); - adapter.setEditing(false); + // TODO [lisa][debug-log-editing] +// setEditing(false); saveMenuItem.setVisible(true); // TODO [greyson][log] Not yet implemented // editMenuItem.setVisible(true); @@ -240,7 +332,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL break; case SUBMITTING: editBanner.setVisibility(View.GONE); - adapter.setEditing(false); +// setEditing(false); editMenuItem.setVisible(false); doneMenuItem.setVisible(false); searchMenuItem.setVisible(false); @@ -248,7 +340,7 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL break; case EDIT: editBanner.setVisibility(View.VISIBLE); - adapter.setEditing(true); +// setEditing(true); editMenuItem.setVisible(false); doneMenuItem.setVisible(true); searchMenuItem.setVisible(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogAdapter.java deleted file mode 100644 index 880a154e43..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogAdapter.java +++ /dev/null @@ -1,188 +0,0 @@ -package org.thoughtcrime.securesms.logsubmit; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.RecyclerView; - -import org.signal.paging.PagingController; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.components.ListenableHorizontalScrollView; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -public class SubmitDebugLogAdapter extends RecyclerView.Adapter { - - private static final int LINE_LENGTH = 500; - - private static final int TYPE_LOG = 1; - private static final int TYPE_PLACEHOLDER = 2; - - private final ScrollManager scrollManager; - private final Listener listener; - private final PagingController pagingController; - private final List lines; - - private boolean editing; - - public SubmitDebugLogAdapter(@NonNull Listener listener, @NonNull PagingController pagingController) { - this.listener = listener; - this.pagingController = pagingController; - this.scrollManager = new ScrollManager(); - this.lines = new ArrayList<>(); - - setHasStableIds(true); - } - - @Override - public long getItemId(int position) { - LogLine item = getItem(position); - return item != null ? getItem(position).getId() : -1; - } - - @Override - public int getItemViewType(int position) { - return getItem(position) == null ? TYPE_PLACEHOLDER : TYPE_LOG; - } - - protected LogLine getItem(int position) { - pagingController.onDataNeededAroundIndex(position); - return lines.get(position); - } - - public void submitList(@NonNull List list) { - this.lines.clear(); - this.lines.addAll(list); - notifyDataSetChanged(); - } - - @Override - public int getItemCount() { - return lines.size(); - } - - @Override - public @NonNull LineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new LineViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.submit_debug_log_line_item, parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull LineViewHolder holder, int position) { - LogLine item = getItem(position); - - if (item == null) { - item = SimpleLogLine.EMPTY; - } - - holder.bind(item, LINE_LENGTH, editing, scrollManager, listener); - } - - @Override - public void onViewRecycled(@NonNull LineViewHolder holder) { - holder.unbind(scrollManager); - } - - public void setEditing(boolean editing) { - this.editing = editing; - notifyDataSetChanged(); - } - - private static class ScrollManager { - private final List listeners = new CopyOnWriteArrayList<>(); - - private int currentPosition; - - void subscribe(@NonNull ScrollObserver observer) { - listeners.add(observer); - observer.onScrollChanged(currentPosition); - } - - void unsubscribe(@NonNull ScrollObserver observer) { - listeners.remove(observer); - } - - void notify(int position) { - currentPosition = position; - - for (ScrollObserver listener : listeners) { - listener.onScrollChanged(position); - } - } - } - - private interface ScrollObserver { - void onScrollChanged(int position); - } - - interface Listener { - void onLogDeleted(@NonNull LogLine logLine); - } - - static class LineViewHolder extends RecyclerView.ViewHolder implements ScrollObserver { - - private final TextView text; - private final ListenableHorizontalScrollView scrollView; - - LineViewHolder(@NonNull View itemView) { - super(itemView); - this.text = itemView.findViewById(R.id.log_item_text); - this.scrollView = itemView.findViewById(R.id.log_item_scroll); - } - - void bind(@NonNull LogLine line, int longestLine, boolean editing, @NonNull ScrollManager scrollManager, @NonNull Listener listener) { - Context context = itemView.getContext(); - - if (line.getText().length() > longestLine) { - text.setText(line.getText().substring(0, longestLine)); - } else if (line.getText().length() < longestLine) { - text.setText(padRight(line.getText(), longestLine)); - } else { - text.setText(line.getText()); - } - - switch (line.getStyle()) { - case NONE: text.setTextColor(ContextCompat.getColor(context, R.color.debuglog_color_none)); break; - case VERBOSE: text.setTextColor(ContextCompat.getColor(context, R.color.debuglog_color_verbose)); break; - case DEBUG: text.setTextColor(ContextCompat.getColor(context, R.color.debuglog_color_debug)); break; - case INFO: text.setTextColor(ContextCompat.getColor(context, R.color.debuglog_color_info)); break; - case WARNING: text.setTextColor(ContextCompat.getColor(context, R.color.debuglog_color_warn)); break; - case ERROR: text.setTextColor(ContextCompat.getColor(context, R.color.debuglog_color_error)); break; - } - - scrollView.setOnScrollListener((newLeft, oldLeft) -> { - if (oldLeft - newLeft != 0) { - scrollManager.notify(newLeft); - } - }); - - scrollManager.subscribe(this); - - if (editing) { - text.setOnClickListener(v -> listener.onLogDeleted(line)); - } else { - text.setOnClickListener(null); - } - } - - void unbind(@NonNull ScrollManager scrollManager) { - text.setOnClickListener(null); - scrollManager.unsubscribe(this); - } - - @Override - public void onScrollChanged(int position) { - scrollView.scrollTo(position, 0); - } - - private static String padRight(String s, int n) { - return String.format("%-" + n + "s", s); - } - } -} 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 842c05c1bf..ba36fdcf24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogViewModel.java @@ -13,11 +13,6 @@ import androidx.lifecycle.ViewModelProvider; import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.signal.core.util.tracing.Tracer; -import org.signal.paging.LivePagedData; -import org.signal.paging.PagedData; -import org.signal.paging.PagingConfig; -import org.signal.paging.PagingController; -import org.signal.paging.ProxyPagingController; import org.thoughtcrime.securesms.database.LogDatabase; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.util.SingleLiveEvent; @@ -32,7 +27,6 @@ public class SubmitDebugLogViewModel extends ViewModel { private final SubmitDebugLogRepository repo; private final MutableLiveData mode; - private final ProxyPagingController pagingController; private final List staticLines; private final MediatorLiveData> lines; private final SingleLiveEvent event; @@ -44,7 +38,6 @@ public class SubmitDebugLogViewModel extends ViewModel { this.repo = new SubmitDebugLogRepository(); this.mode = new MutableLiveData<>(); this.trace = Tracer.getInstance().serialize(); - this.pagingController = new ProxyPagingController<>(); this.firstViewTime = System.currentTimeMillis(); this.staticLines = new ArrayList<>(); this.lines = new MediatorLiveData<>(); @@ -56,17 +49,12 @@ public class SubmitDebugLogViewModel extends ViewModel { Log.blockUntilAllWritesFinished(); LogDatabase.getInstance(AppDependencies.getApplication()).logs().trimToSize(); - LogDataSource dataSource = new LogDataSource(AppDependencies.getApplication(), staticLines, firstViewTime); - PagingConfig config = new PagingConfig.Builder().setPageSize(100) - .setBufferPages(3) - .setStartIndex(0) - .build(); - - LivePagedData pagedData = PagedData.createForLiveData(dataSource, config); + LogDataSource dataSource = new LogDataSource(AppDependencies.getApplication(), staticLines, firstViewTime); + int size = dataSource.size(); + List allLogLines = new ArrayList<>(dataSource.load(0, size, size, () -> false)); ThreadUtil.runOnMain(() -> { - pagingController.set(pagedData.getController()); - lines.addSource(pagedData.getData(), lines::setValue); + lines.setValue(allLogLines); mode.setValue(Mode.NORMAL); }); }); @@ -76,10 +64,6 @@ public class SubmitDebugLogViewModel extends ViewModel { return lines; } - @NonNull PagingController getPagingController() { - return pagingController; - } - @NonNull LiveData getMode() { return mode; } diff --git a/app/src/main/res/layout/submit_debug_log_activity.xml b/app/src/main/res/layout/submit_debug_log_activity.xml index a696a32b48..ca12467610 100644 --- a/app/src/main/res/layout/submit_debug_log_activity.xml +++ b/app/src/main/res/layout/submit_debug_log_activity.xml @@ -44,7 +44,7 @@ app:barrierDirection="bottom" app:constraint_referenced_ids="debug_log_warning_banner,debug_log_edit_banner" /> -