Skip to content

Commit

Permalink
[LogViewer] Improve SearchCriteria
Browse files Browse the repository at this point in the history
logcat has been migrated to `threadtime` format from `time` with backward
compatibility for the latter. Searching now supports Android Studio conventions:
- Supported keywords are: pid and tag, any other non-keyword filtering are
  matched against the log message
- Each keyword takes the intended arguments followed by a : (colon) (e.g., pid:1
  denotes filtering all the logs produced by the PID 1)
- All keywords are and'ed together (e.g., pid:1651 tag:Ime means filtering all
  the logs with the tag containing Ime belonging to the PID 1651)
- Adding a - (minus) before the keyword denotes exclusion (e.g., -pid:13 denotes
  listing all logs except PID 13)
- Adding a ~ (tilde) sign before : (colon) denotes a regular expression match
- Adding a = (equal) sign before : (colon) denotes an exact match (e.g., tag:Ime
  filters any logs with a tag containing the string Ime whereas tag=:Ime filters
  any logs with a tag that is equal to Ime). pid keyword always does an exact
  match
- ~ (tilde) and = (equal) signs are mutually exclusive with ~ (tilde) preferred
  over = (equal), i.e., when both signs are present, only ~ (tilde) sign is used
  for regular expression matching
- Non-exact and non-regex filters are always case insensitive.

Signed-off-by: Muntashir Al-Islam <[email protected]>
  • Loading branch information
MuntashirAkon committed Jan 16, 2025
1 parent 876e9a7 commit bb7e1a4
Show file tree
Hide file tree
Showing 9 changed files with 483 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.github.muntashirakon.AppManager.logcat.helper.PreferenceHelper;
import io.github.muntashirakon.AppManager.logcat.helper.SaveLogHelper;
import io.github.muntashirakon.AppManager.logcat.struct.LogLine;
import io.github.muntashirakon.AppManager.logcat.struct.SearchCriteria;
import io.github.muntashirakon.AppManager.settings.LogViewerPreferences;
import io.github.muntashirakon.AppManager.utils.StoragePermission;
import io.github.muntashirakon.AppManager.utils.UIUtils;
Expand All @@ -58,7 +59,8 @@ public abstract class AbsLogViewerFragment extends Fragment implements MenuProvi
protected LogViewerRecyclerAdapter mLogListAdapter;

protected boolean mAutoscrollToBottom = true;
protected volatile String mQueryString;
@Nullable
protected volatile SearchCriteria mSearchCriteria;

protected final StoragePermission mStoragePermission = StoragePermission.init(this);
protected final RecyclerView.OnScrollListener mRecyclerViewScrollListener = new RecyclerView.OnScrollListener() {
Expand Down Expand Up @@ -107,9 +109,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
mRecyclerView.setItemAnimator(null);
// Check for query string
mQueryString = mActivity.getSearchQuery();
if (mQueryString != null) {
mRecyclerView.postDelayed(() -> mActivity.search(mQueryString), 1000);
mSearchCriteria = mActivity.getSearchQuery();
if (mSearchCriteria != null) {
mRecyclerView.postDelayed(() -> mActivity.search(mSearchCriteria), 1000);
}
mLogListAdapter = new LogViewerRecyclerAdapter();
mLogListAdapter.setClickListener(mActivity);
Expand Down Expand Up @@ -197,7 +199,7 @@ public boolean onMenuItemSelected(@NonNull MenuItem item) {
.setOnSingleChoiceClickListener((dialog, which, selectedLogLevel, isChecked) -> {
mViewModel.setLogLevel(selectedLogLevel);
// Search again
mActivity.search(mQueryString);
mActivity.search(mSearchCriteria);
})
.setNegativeButton(R.string.close, null)
.show();
Expand All @@ -216,10 +218,10 @@ public boolean onMenuItemSelected(@NonNull MenuItem item) {

@CallSuper
@Override
public void onQuery(@Nullable String searchTerm) {
mQueryString = searchTerm;
public void onQuery(@Nullable SearchCriteria searchCriteria) {
mSearchCriteria = searchCriteria;
Filter filter = mLogListAdapter.getFilter();
filter.filter(searchTerm, this);
filter.filter(searchCriteria != null ? searchCriteria.query : null, this);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public boolean onMenuItemSelected(@NonNull MenuItem item) {
public void onNewLogsAvailable(@NonNull List<LogLine> logLines) {
mActivity.hideProgressBar();
for (LogLine logLine : logLines) {
mLogListAdapter.addWithFilter(logLine, mQueryString, true);
mLogListAdapter.addWithFilter(logLine, mSearchCriteria, true);
mActivity.addToAutocompleteSuggestions(logLine);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public class LogViewerActivity extends BaseActivity implements SearchView.OnQuer
public static final String TAG = LogViewerActivity.class.getSimpleName();

public interface SearchingInterface {
void onQuery(@Nullable String searchTerm);
void onQuery(@Nullable SearchCriteria searchCriteria);
}

public static final String EXTRA_FILTER = "filter";
Expand All @@ -97,7 +97,8 @@ public interface SearchingInterface {
@Nullable
private AlertDialog mLoadingDialog;
private SearchView mSearchView;
private String mSearchQuery;
@Nullable
private SearchCriteria mSearchCriteria;
private boolean mDynamicallyEnteringSearchQuery;
private final Set<String> mSearchSuggestionsSet = new HashSet<>();
private CursorAdapter mSearchSuggestionsAdapter;
Expand Down Expand Up @@ -289,7 +290,7 @@ private void applyFiltersFromIntent(@Nullable Intent intent) {
UIUtils.displayLongToast(R.string.toast_invalid_level, level);
} else {
mViewModel.setLogLevel(logLevelLimit);
search(mSearchQuery);
search(mSearchCriteria);
}
}
}
Expand All @@ -304,7 +305,7 @@ public void onResume() {
}

@Override
protected void onNewIntent(Intent intent) {
protected void onNewIntent(@NonNull Intent intent) {
super.onNewIntent(intent);
applyFiltersFromIntent(intent);
Uri dataUri = IntentCompat.getDataUri(intent);
Expand Down Expand Up @@ -345,7 +346,7 @@ private void startLiveLogViewer(boolean force) {
.commit();
}

private void populateSuggestionsAdapter(String query) {
private void populateSuggestionsAdapter(@Nullable String query) {
final MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, "suggestion"});
List<String> suggestionsForQuery = getSuggestionsForQuery(query);
for (int i = 0, suggestionsForQuerySize = suggestionsForQuery.size(); i < suggestionsForQuerySize; i++) {
Expand All @@ -355,7 +356,8 @@ private void populateSuggestionsAdapter(String query) {
mSearchSuggestionsAdapter.changeCursor(c);
}

private List<String> getSuggestionsForQuery(String query) {
@NonNull
private List<String> getSuggestionsForQuery(@Nullable String query) {
List<String> suggestions = new ArrayList<>(mSearchSuggestionsSet);
Collections.sort(suggestions, String.CASE_INSENSITIVE_ORDER);
List<String> actualSuggestions = new ArrayList<>();
Expand All @@ -382,7 +384,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
@Override
public boolean onSearchByClick(MenuItem item, LogLine logLine) {
if (logLine != null) {
if (logLine.getProcessId() == -1) {
if (logLine.getPid() == -1) {
// invalid line
return false;
}
Expand All @@ -400,7 +402,7 @@ public boolean onQueryTextSubmit(String query) {
@Override
public boolean onQueryTextChange(String newText) {
if (!mDynamicallyEnteringSearchQuery) {
search(newText);
search(new SearchCriteria(newText));
populateSuggestionsAdapter(newText);
}
mDynamicallyEnteringSearchQuery = false;
Expand All @@ -414,7 +416,7 @@ public boolean onSuggestionSelect(int position) {

@Override
public boolean onSuggestionClick(int position) {
List<String> suggestions = getSuggestionsForQuery(mSearchQuery);
List<String> suggestions = getSuggestionsForQuery(mSearchCriteria != null ? mSearchCriteria.query : null);
if (!suggestions.isEmpty()) {
mSearchView.setQuery(suggestions.get(position), true);
}
Expand Down Expand Up @@ -474,7 +476,7 @@ private void showSearchByDialog(final LogLine logLine) {
TextView pidText = pid.getEditText();

Objects.requireNonNull(tagText).setText(logLine.getTagName());
Objects.requireNonNull(pidText).setText(String.valueOf(logLine.getProcessId()));
Objects.requireNonNull(pidText).setText(String.valueOf(logLine.getPid()));

tag.setEndIconOnClickListener(v -> {
String tagQuery = (logLine.getTagName().contains(" ")) ? ('"' + logLine.getTagName() + '"') : logLine.getTagName();
Expand All @@ -483,7 +485,7 @@ private void showSearchByDialog(final LogLine logLine) {
});

pid.setEndIconOnClickListener(v -> {
setSearchQuery(SearchCriteria.PID_KEYWORD + logLine.getProcessId());
setSearchQuery(SearchCriteria.PID_KEYWORD + logLine.getPid());
dialog.dismiss();
});
}
Expand Down Expand Up @@ -586,36 +588,37 @@ private void resetDisplay() {

private void resetFilter() {
mViewModel.setLogLevel(Prefs.LogViewer.getLogLevel());
search(mSearchQuery);
search(mSearchCriteria);
}

private void setSearchQuery(String text) {
// Sets the search text without invoking autosuggestions, which are really only useful when typing
mDynamicallyEnteringSearchQuery = true;
search(text);
search(new SearchCriteria(text));
mSearchView.setIconified(false);
mSearchView.setQuery(mSearchQuery, true);
mSearchView.setQuery(mSearchCriteria != null ? mSearchCriteria.query : null, true);
mSearchView.clearFocus();
}

String getSearchQuery() {
return mSearchQuery;
@Nullable
SearchCriteria getSearchQuery() {
return mSearchCriteria;
}

void search(String filterText) {
void search(@Nullable SearchCriteria searchCriteria) {
if (mSearchingInterface != null) {
mSearchingInterface.onQuery(filterText);
mSearchingInterface.onQuery(searchCriteria);
}
mSearchQuery = filterText;
if (!TextUtils.isEmpty(mSearchQuery)) {
mSearchCriteria = searchCriteria;
if (mSearchCriteria == null || !TextUtils.isEmpty(mSearchCriteria.query)) {
mDynamicallyEnteringSearchQuery = true;
}
}

private void addToAutocompleteSuggestions(String trimmed) {
if (mSearchSuggestionsSet.size() < MAX_NUM_SUGGESTIONS && !mSearchSuggestionsSet.contains(trimmed)) {
mSearchSuggestionsSet.add(trimmed);
populateSuggestionsAdapter(mSearchQuery);
populateSuggestionsAdapter(mSearchCriteria != null ? mSearchCriteria.query : null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import io.github.muntashirakon.AppManager.BuildConfig;
Expand Down Expand Up @@ -156,13 +155,13 @@ public void readAll(LogLine object, boolean notify) {
}
}

public void addWithFilter(@NonNull LogLine object, @Nullable CharSequence text, boolean notify) {
public void addWithFilter(@NonNull LogLine object, @Nullable SearchCriteria searchCriteria, boolean notify) {
if (mOriginalValues != null) {
List<LogLine> inputList = Collections.singletonList(object);
if (mFilter == null) {
mFilter = new ArrayFilter();
}
List<LogLine> filteredObjects = mFilter.performFilteringOnList(inputList, text);
List<LogLine> filteredObjects = mFilter.performFilteringOnList(inputList, searchCriteria);
synchronized (mLock) {
mOriginalValues.add(object);
mObjects.addAll(filteredObjects);
Expand Down Expand Up @@ -370,7 +369,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
View contentView = holder.itemView.findViewById(R.id.log_content);
contentView.setBackgroundResource(position % 2 == 0 ? io.github.muntashirakon.ui.R.drawable.item_semi_transparent : io.github.muntashirakon.ui.R.drawable.item_transparent);

//OUTPUT TEXT VIEW
// Display message
TextView output = holder.output;
output.setSingleLine(!logLine.isExpanded());
output.setText(logLine.getLogOutput());
Expand All @@ -382,7 +381,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
tag.setVisibility(logLine.getLogLevel() == -1 ? View.GONE : View.VISIBLE);

//EXPANDED INFO
boolean extraInfoIsVisible = logLine.isExpanded() && logLine.getProcessId() != -1 // -1 marks lines like 'beginning of /dev/log...'
boolean extraInfoIsVisible = logLine.isExpanded() && logLine.getPid() != -1 // -1 marks lines like 'beginning of /dev/log...'
&& Prefs.LogViewer.showPidTidTimestamp();

TextView pidText = holder.pid;
Expand All @@ -391,7 +390,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
timestampText.setVisibility(extraInfoIsVisible ? View.VISIBLE : View.GONE);

if (extraInfoIsVisible) {
pidText.setText(logLine.getProcessId() != -1 ? Integer.toString(logLine.getProcessId()) : null);
pidText.setText(logLine.getPid() != -1 ? Integer.toString(logLine.getPid()) : null);
timestampText.setText(logLine.getTimestamp());
}

Expand Down Expand Up @@ -523,17 +522,16 @@ protected FilterResults performFiltering(CharSequence prefix) {
}
}

ArrayList<LogLine> allValues = performFilteringOnList(mOriginalValues, prefix);
SearchCriteria searchCriteria = new SearchCriteria(prefix != null ? prefix.toString() : null);
ArrayList<LogLine> allValues = performFilteringOnList(mOriginalValues, searchCriteria);

results.values = allValues;
results.count = allValues.size();

return results;
}

public ArrayList<LogLine> performFilteringOnList(List<LogLine> inputList, CharSequence query) {
SearchCriteria searchCriteria = new SearchCriteria(query);

public ArrayList<LogLine> performFilteringOnList(List<LogLine> inputList, @Nullable SearchCriteria searchCriteria) {
// search by log level
ArrayList<LogLine> allValues = new ArrayList<>();

Expand All @@ -550,7 +548,7 @@ public ArrayList<LogLine> performFilteringOnList(List<LogLine> inputList, CharSe
ArrayList<LogLine> finalValues = allValues;

// search by criteria
if (!searchCriteria.isEmpty()) {
if (searchCriteria != null && !searchCriteria.isEmpty()) {
final int count = allValues.size();
final ArrayList<LogLine> newValues = new ArrayList<>(count);
for (final LogLine value : allValues) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.logcat.struct.LogLine;
import io.github.muntashirakon.AppManager.logcat.struct.SearchCriteria;
import io.github.muntashirakon.AppManager.utils.ContextUtils;
import io.github.muntashirakon.AppManager.utils.ThreadUtils;
import io.github.muntashirakon.AppManager.utils.Utils;
Expand Down Expand Up @@ -81,7 +82,7 @@ public boolean onMenuItemSelected(@NonNull MenuItem item) {
public void onNewLogsAvailable(@NonNull List<LogLine> logLines) {
mActivity.hideProgressBar();
for (LogLine logLine : logLines) {
mLogListAdapter.addWithFilter(logLine, "", true);
mLogListAdapter.addWithFilter(logLine, new SearchCriteria(null), true);
mActivity.addToAutocompleteSuggestions(logLine);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,14 @@ public static String getLastLogLine(@LogBufferId int buffers) {
}

@NonNull
private static String[] getLogcatArgs(@LogBufferId int buffers, boolean dumpAndExit) {
List<String> args = new ArrayList<>(Arrays.asList("logcat", "-v", "time"));
public static String[] getLogcatArgs(@LogBufferId int buffers, boolean dumpAndExit) {
// https://cs.android.com/android/platform/superproject/main/+/main:system/logging/liblog/logprint.cpp;l=1547;drc=b4d6320e2ae398b36f0aaafb2ecd83609d2d99af
// threadtime: <time:%m-%d %H:%M:%S.%03ld> <uid:%5s> <pid:%5d> <tid:%5d> <level:%c> <tag:%s\s+>: <message>
// Modifiers:
// - uid: Display UID (Android 7 onwards)
// - descriptive: Descriptive output, currently NOP (Android 8 onwards)
// * UID is not guaranteed
List<String> args = new ArrayList<>(Arrays.asList("logcat", "-v", "threadtime"));

if (buffers == LOG_ID_ALL) {
args.add("-b");
Expand Down
Loading

0 comments on commit bb7e1a4

Please sign in to comment.