manager: Add an option to exclude the current application and certain system calls from the log viewer.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -41,6 +42,8 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.time.*
|
import java.time.*
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import android.os.Process.myUid
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
private val SPACING_SMALL = 4.dp
|
private val SPACING_SMALL = 4.dp
|
||||||
private val SPACING_MEDIUM = 8.dp
|
private val SPACING_MEDIUM = 8.dp
|
||||||
@@ -65,9 +68,30 @@ enum class LogType(val displayName: String, val color: Color) {
|
|||||||
UNKNOWN("UNKNOWN", Color(0xFF757575))
|
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 utcFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
private val localFormatter = 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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
@@ -83,14 +107,40 @@ fun LogViewerScreen(navigator: DestinationsNavigator) {
|
|||||||
var filterType by rememberSaveable { mutableStateOf<LogType?>(null) }
|
var filterType by rememberSaveable { mutableStateOf<LogType?>(null) }
|
||||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||||
var showSearchBar by rememberSaveable { mutableStateOf(false) }
|
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 ->
|
logEntries.filter { entry ->
|
||||||
val matchesFilter = filterType == null || entry.type == filterType
|
|
||||||
val matchesSearch = searchQuery.isEmpty() ||
|
val matchesSearch = searchQuery.isEmpty() ||
|
||||||
entry.comm.contains(searchQuery, ignoreCase = true) ||
|
entry.comm.contains(searchQuery, ignoreCase = true) ||
|
||||||
entry.details.contains(searchQuery, ignoreCase = true) ||
|
entry.details.contains(searchQuery, ignoreCase = true) ||
|
||||||
entry.uid.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
|
matchesFilter && matchesSearch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +211,14 @@ fun LogViewerScreen(navigator: DestinationsNavigator) {
|
|||||||
filterType = filterType,
|
filterType = filterType,
|
||||||
onFilterTypeSelected = { filterType = it },
|
onFilterTypeSelected = { filterType = it },
|
||||||
logCount = filteredEntries.size,
|
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?,
|
filterType: LogType?,
|
||||||
onFilterTypeSelected: (LogType?) -> Unit,
|
onFilterTypeSelected: (LogType?) -> Unit,
|
||||||
logCount: Int,
|
logCount: Int,
|
||||||
totalCount: Int
|
totalCount: Int,
|
||||||
|
excludedSubTypes: Set<LogExclType>,
|
||||||
|
onExcludeToggle: (LogExclType) -> Unit
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -273,6 +332,35 @@ private fun LogControlPanel(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
|
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(
|
||||||
text = stringResource(R.string.log_viewer_showing_entries, logCount, totalCount),
|
text = stringResource(R.string.log_viewer_showing_entries, logCount, totalCount),
|
||||||
|
|||||||
@@ -707,4 +707,6 @@
|
|||||||
<string name="log_viewer_clear_search">清除搜索</string>
|
<string name="log_viewer_clear_search">清除搜索</string>
|
||||||
<string name="log_viewer_view_logs">查看使用日志</string>
|
<string name="log_viewer_view_logs">查看使用日志</string>
|
||||||
<string name="log_viewer_view_logs_summary">查看 KernelSU 超级用户访问日志</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>
|
</resources>
|
||||||
|
|||||||
@@ -716,4 +716,6 @@ Important Note:\n
|
|||||||
<string name="log_viewer_clear_search">Clear search</string>
|
<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">View Usage Logs</string>
|
||||||
<string name="log_viewer_view_logs_summary">View KernelSU superuser access 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>
|
</resources>
|
||||||
Reference in New Issue
Block a user