manager: Add an option to exclude the current application and certain system calls from the log viewer.

This commit is contained in:
ShirkNeko
2025-10-22 22:42:26 +08:00
parent e9ee2304d3
commit 7bf13fbfca
3 changed files with 96 additions and 4 deletions

View File

@@ -1,5 +1,6 @@
package com.sukisu.ultra.ui.screen
import android.content.Context
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -41,6 +42,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.*
import java.time.format.DateTimeFormatter
import android.os.Process.myUid
import androidx.core.content.edit
private val SPACING_SMALL = 4.dp
private val SPACING_MEDIUM = 8.dp
@@ -65,9 +68,30 @@ enum class LogType(val displayName: String, val color: Color) {
UNKNOWN("UNKNOWN", Color(0xFF757575))
}
enum class LogExclType(val displayName: String, val color: Color) {
CURRENT_APP("Current app", Color(0xFF9E9E9E)),
PRCTL_STAR("prctl_*", Color(0xFF00BCD4)),
PRCTL_UNKNOWN("prctl_unknown", Color(0xFF00BCD4)),
SETUID("setuid", Color(0xFF00BCD4))
}
private val utcFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
private val localFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
private fun saveExcludedSubTypes(context: Context, types: Set<LogExclType>) {
val prefs = context.getSharedPreferences("sulog", Context.MODE_PRIVATE)
val nameSet = types.map { it.name }.toSet()
prefs.edit { putStringSet("excluded_subtypes", nameSet) }
}
private fun loadExcludedSubTypes(context: Context): Set<LogExclType> {
val prefs = context.getSharedPreferences("sulog", Context.MODE_PRIVATE)
val nameSet = prefs.getStringSet("excluded_subtypes", emptySet()) ?: emptySet()
return nameSet.mapNotNull { name ->
LogExclType.entries.firstOrNull { it.name == name }
}.toSet()
}
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
@@ -83,14 +107,40 @@ fun LogViewerScreen(navigator: DestinationsNavigator) {
var filterType by rememberSaveable { mutableStateOf<LogType?>(null) }
var searchQuery by rememberSaveable { mutableStateOf("") }
var showSearchBar by rememberSaveable { mutableStateOf(false) }
val currentUid = remember { myUid().toString() }
val filteredEntries = remember(logEntries, filterType, searchQuery) {
val initialExcluded = remember {
loadExcludedSubTypes(context)
}
var excludedSubTypes by rememberSaveable { mutableStateOf(initialExcluded) }
LaunchedEffect(excludedSubTypes) {
saveExcludedSubTypes(context, excludedSubTypes)
}
val filteredEntries = remember(
logEntries, filterType, searchQuery, excludedSubTypes
) {
logEntries.filter { entry ->
val matchesFilter = filterType == null || entry.type == filterType
val matchesSearch = searchQuery.isEmpty() ||
entry.comm.contains(searchQuery, ignoreCase = true) ||
entry.details.contains(searchQuery, ignoreCase = true) ||
entry.uid.contains(searchQuery, ignoreCase = true)
// 排除本应用
if (LogExclType.CURRENT_APP in excludedSubTypes && entry.uid == currentUid) return@filter false
// 排除 SYSCALL 子类型
if (entry.type == LogType.SYSCALL) {
val detail = entry.details
if (LogExclType.PRCTL_STAR in excludedSubTypes && detail.startsWith("Syscall: prctl") && !detail.startsWith("Syscall: prctl_unknown")) return@filter false
if (LogExclType.PRCTL_UNKNOWN in excludedSubTypes && detail.startsWith("Syscall: prctl_unknown")) return@filter false
if (LogExclType.SETUID in excludedSubTypes && detail.startsWith("Syscall: setuid")) return@filter false
}
// 普通类型筛选
val matchesFilter = filterType == null || entry.type == filterType
matchesFilter && matchesSearch
}
}
@@ -161,7 +211,14 @@ fun LogViewerScreen(navigator: DestinationsNavigator) {
filterType = filterType,
onFilterTypeSelected = { filterType = it },
logCount = filteredEntries.size,
totalCount = logEntries.size
totalCount = logEntries.size,
excludedSubTypes = excludedSubTypes,
onExcludeToggle = { excl ->
excludedSubTypes = if (excl in excludedSubTypes)
excludedSubTypes - excl
else
excludedSubTypes + excl
}
)
// 日志列表
@@ -200,7 +257,9 @@ private fun LogControlPanel(
filterType: LogType?,
onFilterTypeSelected: (LogType?) -> Unit,
logCount: Int,
totalCount: Int
totalCount: Int,
excludedSubTypes: Set<LogExclType>,
onExcludeToggle: (LogExclType) -> Unit
) {
Card(
modifier = Modifier
@@ -273,6 +332,35 @@ private fun LogControlPanel(
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
Text(
text = stringResource(R.string.log_viewer_exclude_subtypes),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
LazyRow(horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)) {
items(LogExclType.entries.toTypedArray()) { excl ->
val label = if (excl == LogExclType.CURRENT_APP)
stringResource(R.string.log_viewer_exclude_current_app)
else excl.displayName
FilterChip(
onClick = { onExcludeToggle(excl) },
label = { Text(label) },
selected = excl in excludedSubTypes,
leadingIcon = {
Box(
modifier = Modifier
.size(8.dp)
.background(excl.color, RoundedCornerShape(4.dp))
)
}
)
}
}
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
// 统计信息
Text(
text = stringResource(R.string.log_viewer_showing_entries, logCount, totalCount),

View File

@@ -707,4 +707,6 @@
<string name="log_viewer_clear_search">清除搜索</string>
<string name="log_viewer_view_logs">查看使用日志</string>
<string name="log_viewer_view_logs_summary">查看 KernelSU 超级用户访问日志</string>
<string name="log_viewer_exclude_subtypes">排除子类型</string>
<string name="log_viewer_exclude_current_app">当前应用</string>
</resources>

View File

@@ -716,4 +716,6 @@ Important Note:\n
<string name="log_viewer_clear_search">Clear search</string>
<string name="log_viewer_view_logs">View Usage Logs</string>
<string name="log_viewer_view_logs_summary">View KernelSU superuser access logs</string>
<string name="log_viewer_exclude_subtypes">Exclude sub-types</string>
<string name="log_viewer_exclude_current_app">Current App</string>
</resources>