View debug log screen through WebView through a module.

This commit is contained in:
lisa-signal
2025-07-30 14:47:50 -04:00
committed by GitHub
parent 1f243bca74
commit 02420fce2a
30 changed files with 999 additions and 340 deletions

View File

@@ -1,50 +0,0 @@
package org.thoughtcrime.securesms.logsubmit
import android.app.Application
import org.signal.core.util.logging.Scrubber
import org.signal.paging.PagedDataSource
import org.thoughtcrime.securesms.database.LogDatabase
/**
* Retrieves logs to show in the [SubmitDebugLogActivity].
*
* @param prefixLines A static list of lines to show before all of the lines retrieved from [LogDatabase]
* @param untilTime Only show logs before this time. This is our way of making sure the set of logs we show on this screen doesn't grow.
*/
class LogDataSource(
application: Application,
private val prefixLines: List<LogLine>,
private val untilTime: Long
) :
PagedDataSource<Long, LogLine> {
val logDatabase = LogDatabase.getInstance(application)
override fun size(): Int {
return prefixLines.size + logDatabase.logs.getLogCountBeforeTime(untilTime)
}
override fun load(start: Int, length: Int, totalSize: Int, cancellationSignal: PagedDataSource.CancellationSignal): List<LogLine> {
if (start + length < prefixLines.size) {
return prefixLines.subList(start, start + length)
} else if (start < prefixLines.size) {
return prefixLines.subList(start, prefixLines.size) +
logDatabase.logs.getRangeBeforeTime(0, length - (prefixLines.size - start), untilTime).map { convertToLogLine(it) }
} else {
return logDatabase.logs.getRangeBeforeTime(start - prefixLines.size, length, untilTime).map { convertToLogLine(it) }
}
}
override fun load(key: Long?): LogLine? {
throw UnsupportedOperationException("Not implemented!")
}
override fun getKey(data: LogLine): Long {
return data.id
}
private fun convertToLogLine(raw: String): LogLine {
val scrubbed: String = Scrubber.scrub(raw).toString()
return SimpleLogLine(scrubbed, LogStyleParser.parseStyle(scrubbed), LogLine.Placeholder.NONE)
}
}

View File

@@ -11,21 +11,20 @@ import android.text.util.Linkify;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebView;
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;
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.signal.debuglogsviewer.DebugLogsViewer;
import org.thoughtcrime.securesms.BaseActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.ProgressCard;
@@ -36,14 +35,14 @@ import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
import java.util.ArrayList;
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 View warningBanner;
@@ -58,6 +57,9 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
private MenuItem searchMenuItem;
private MenuItem saveMenuItem;
private boolean isWebViewLoaded;
private boolean hasPresentedLines;
private final DynamicTheme dynamicTheme = new DynamicTheme();
@Override
@@ -89,35 +91,33 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
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() {
@Override
public boolean onQueryTextSubmit(String query) {
viewModel.onQueryUpdated(query);
return true;
}
@Override
public boolean onQueryTextChange(String query) {
viewModel.onQueryUpdated(query);
return true;
}
};
searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
searchView.setOnQueryTextListener(queryListener);
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
searchView.setOnQueryTextListener(null);
viewModel.onSearchClosed();
return true;
}
});
// TODO [lisa][debug-log-search]
// SearchView searchView = (SearchView) searchMenuItem.getActionView();
// SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() {
// @Override
// public boolean onQueryTextSubmit(String query) {
// return true;
// }
//
// @Override
// public boolean onQueryTextChange(String query) {
// return true;
// }
// };
//
// searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
// @Override
// public boolean onMenuItemActionExpand(MenuItem item) {
// searchView.setOnQueryTextListener(queryListener);
// return true;
// }
//
// @Override
// public boolean onMenuItemActionCollapse(MenuItem item) {
// searchView.setOnQueryTextListener(null);
// return true;
// }
// });
return true;
}
@@ -165,13 +165,8 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
}
}
@Override
public void onLogDeleted(@NonNull LogLine logLine) {
viewModel.onLogDeleted(logLine);
}
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 +174,16 @@ 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);
DebugLogsViewer.initWebView(logWebView, this, () -> {
isWebViewLoaded = true;
presentLines((viewModel.getLines().getValue() != null) ? viewModel.getLines().getValue() : new ArrayList<>());
});
submitButton.setOnClickListener(v -> onSubmitClicked());
scrollToTopButton.setOnClickListener(v -> DebugLogsViewer.scrollToTop(logWebView));
scrollToBottomButton.setOnClickListener(v -> DebugLogsViewer.scrollToBottom(logWebView));
scrollToBottomButton.setOnClickListener(v -> lineList.scrollToPosition(adapter.getItemCount() - 1));
scrollToTopButton.setOnClickListener(v -> lineList.scrollToPosition(0));
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);
}
if (((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition() > 10) {
scrollToTopButton.setVisibility(View.VISIBLE);
} else {
scrollToTopButton.setVisibility(View.GONE);
}
}
});
this.progressCard.setVisibility(View.VISIBLE);
}
private void initViewModel() {
@@ -217,21 +193,35 @@ public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugL
}
private void presentLines(@NonNull List<LogLine> lines) {
if (progressCard != null && lines.size() > 0) {
progressCard.setVisibility(View.GONE);
warningBanner.setVisibility(View.VISIBLE);
submitButton.setVisibility(View.VISIBLE);
if (!isWebViewLoaded || hasPresentedLines) {
return;
}
adapter.submitList(lines);
if (progressCard != null && lines.size() > 0) {
progressCard.setVisibility(View.GONE);
warningBanner.setVisibility(View.VISIBLE);
submitButton.setVisibility(View.VISIBLE);
hasPresentedLines = true;
}
StringBuilder lineBuilder = new StringBuilder();
for (LogLine line : lines) {
if (line == null) continue;
lineBuilder.append(String.format("%s\n", line.getText()));
}
DebugLogsViewer.presentLines(logWebView, lineBuilder.toString());
}
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 +230,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 +238,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);

View File

@@ -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<SubmitDebugLogAdapter.LineViewHolder> {
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<LogLine> 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<LogLine> 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<ScrollObserver> 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);
}
}
}

View File

@@ -11,13 +11,9 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
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,42 +28,43 @@ public class SubmitDebugLogViewModel extends ViewModel {
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;
private final List<LogLine> allLines;
private SubmitDebugLogViewModel() {
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<>();
this.event = new SingleLiveEvent<>();
this.allLines = new ArrayList<>();
repo.getPrefixLogLines(staticLines -> {
this.staticLines.addAll(staticLines);
Log.blockUntilAllWritesFinished();
LogDatabase.getInstance(AppDependencies.getApplication()).logs().trimToSize();
SignalExecutors.UNBOUNDED.execute(() -> {
allLines.clear();
allLines.addAll(staticLines);
LogDataSource dataSource = new LogDataSource(AppDependencies.getApplication(), staticLines, firstViewTime);
PagingConfig config = new PagingConfig.Builder().setPageSize(100)
.setBufferPages(3)
.setStartIndex(0)
.build();
try (LogDatabase.LogTable.CursorReader logReader = (LogDatabase.LogTable.CursorReader) LogDatabase.getInstance(AppDependencies.getApplication()).logs().getAllBeforeTime(firstViewTime)) {
while (logReader.hasNext()) {
String next = logReader.next();
allLines.add(new SimpleLogLine(next, LogStyleParser.parseStyle(next), LogLine.Placeholder.NONE));
}
}
LivePagedData<Long, LogLine> pagedData = PagedData.createForLiveData(dataSource, config);
ThreadUtil.runOnMain(() -> {
pagingController.set(pagedData.getController());
lines.addSource(pagedData.getData(), lines::setValue);
mode.setValue(Mode.NORMAL);
ThreadUtil.runOnMain(() -> {
lines.setValue(allLines);
mode.setValue(Mode.NORMAL);
});
});
});
}
@@ -76,10 +73,6 @@ public class SubmitDebugLogViewModel extends ViewModel {
return lines;
}
@NonNull PagingController getPagingController() {
return pagingController;
}
@NonNull LiveData<Mode> getMode() {
return mode;
}
@@ -161,4 +154,4 @@ public class SubmitDebugLogViewModel extends ViewModel {
return modelClass.cast(new SubmitDebugLogViewModel());
}
}
}
}

View File

@@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord
import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord.ActionId
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.megaphone.RemoteMegaphoneRepository.Action
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.LocaleRemoteConfig

View File

@@ -18,7 +18,7 @@ public final class ContextUtil {
}
/**
* Implementation "borrowed" from com.airbnb.lottie.utils.Utils#getAnimationScale(android.content.Context)
* Implementation "borrowed" from com.airbnb.lottie.utils.DebugLogViewer#getAnimationScale(android.content.Context)
*/
public static float getAnimationScale(Context context) {
return Settings.Global.getFloat(context.getContentResolver(), Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f);