manager:Add SuSFS to obtain slot uname and build time information
This commit is contained in:
@@ -77,6 +77,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.SuSFSConfigScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.sukisu.ultra.KernelVersion
|
||||
import com.sukisu.ultra.Natives
|
||||
@@ -91,6 +92,8 @@ import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.util.checkNewVersion
|
||||
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||
import com.sukisu.ultra.ui.util.reboot
|
||||
import com.sukisu.ultra.ui.util.getSuSFS
|
||||
import com.sukisu.ultra.ui.util.SuSFSManager
|
||||
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -127,7 +130,8 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
scrollBehavior = scrollBehavior
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigator = navigator
|
||||
)
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(
|
||||
@@ -264,8 +268,10 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
navigator: DestinationsNavigator
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val cardColor = if (CardConfig.isCustomBackgroundEnabled) {
|
||||
colorScheme.surfaceContainerLow
|
||||
@@ -285,6 +291,19 @@ private fun TopBar(
|
||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||
),
|
||||
actions = {
|
||||
// SuSFS 配置按钮
|
||||
if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) {
|
||||
IconButton(onClick = {
|
||||
navigator.navigate(SuSFSConfigScreenDestination)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Tune,
|
||||
contentDescription = stringResource(R.string.susfs_config_setting_title)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 重启按钮
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
KsuIsValid {
|
||||
IconButton(onClick = {
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.RestoreFromTrash
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Storage
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
@@ -122,6 +123,12 @@ fun SuSFSConfigScreen(
|
||||
var lastAppliedBuildTime by remember { mutableStateOf("") }
|
||||
var executeInPostFsData by remember { mutableStateOf(false) } // 是否在post-fs-data中执行
|
||||
|
||||
// 槽位信息相关状态
|
||||
var slotInfoList by remember { mutableStateOf(emptyList<SuSFSManager.SlotInfo>()) }
|
||||
var currentActiveSlot by remember { mutableStateOf("") }
|
||||
var isLoadingSlotInfo by remember { mutableStateOf(false) }
|
||||
var showSlotInfoDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// 路径管理相关状态
|
||||
var susPaths by remember { mutableStateOf(emptySet<String>()) }
|
||||
var susMounts by remember { mutableStateOf(emptySet<String>()) }
|
||||
@@ -167,6 +174,16 @@ fun SuSFSConfigScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 加载槽位信息
|
||||
fun loadSlotInfo() {
|
||||
coroutineScope.launch {
|
||||
isLoadingSlotInfo = true
|
||||
slotInfoList = SuSFSManager.getCurrentSlotInfo()
|
||||
currentActiveSlot = SuSFSManager.getCurrentActiveSlot()
|
||||
isLoadingSlotInfo = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载当前配置
|
||||
LaunchedEffect(Unit) {
|
||||
unameValue = SuSFSManager.getUnameValue(context)
|
||||
@@ -180,6 +197,9 @@ fun SuSFSConfigScreen(
|
||||
tryUmounts = SuSFSManager.getTryUmounts(context)
|
||||
androidDataPath = SuSFSManager.getAndroidDataPath(context)
|
||||
sdcardPath = SuSFSManager.getSdcardPath(context)
|
||||
|
||||
// 加载槽位信息
|
||||
loadSlotInfo()
|
||||
}
|
||||
|
||||
// 当切换到启用功能状态标签页时加载数据
|
||||
@@ -197,6 +217,153 @@ fun SuSFSConfigScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 槽位信息对话框
|
||||
if (showSlotInfoDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showSlotInfoDialog = false },
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.susfs_slot_info_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.susfs_current_active_slot, currentActiveSlot),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
if (slotInfoList.isNotEmpty()) {
|
||||
slotInfoList.forEach { slotInfo ->
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (slotInfo.slotName == currentActiveSlot) {
|
||||
MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
|
||||
}
|
||||
),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Storage,
|
||||
contentDescription = null,
|
||||
tint = if (slotInfo.slotName == currentActiveSlot) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
},
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = slotInfo.slotName,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = if (slotInfo.slotName == currentActiveSlot) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
)
|
||||
if (slotInfo.slotName == currentActiveSlot) {
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Surface(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.susfs_slot_current_badge),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.susfs_slot_uname, slotInfo.uname),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.susfs_slot_build_time, slotInfo.buildTime),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
unameValue = slotInfo.uname
|
||||
showSlotInfoDialog = false
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
shape = RoundedCornerShape(6.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.susfs_slot_use_uname), fontSize = 12.sp)
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
buildTimeValue = slotInfo.buildTime
|
||||
showSlotInfoDialog = false
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
shape = RoundedCornerShape(6.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.susfs_slot_use_build_time), fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(R.string.susfs_slot_info_unavailable),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = { loadSlotInfo() },
|
||||
enabled = !isLoadingSlotInfo,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.refresh))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = { showSlotInfoDialog = false },
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.close))
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// 各种对话框的定义保持不变
|
||||
// 添加路径对话框
|
||||
if (showAddPathDialog) {
|
||||
@@ -940,6 +1107,7 @@ fun SuSFSConfigScreen(
|
||||
}
|
||||
}
|
||||
},
|
||||
onShowSlotInfo = { showSlotInfoDialog = true },
|
||||
context = context
|
||||
)
|
||||
}
|
||||
@@ -1043,6 +1211,7 @@ private fun BasicSettingsContent(
|
||||
canEnableAutoStart: Boolean,
|
||||
isLoading: Boolean,
|
||||
onAutoStartToggle: (Boolean) -> Unit,
|
||||
onShowSlotInfo: () -> Unit,
|
||||
context: android.content.Context
|
||||
) {
|
||||
var scriptLocationExpanded by remember { mutableStateOf(false) }
|
||||
@@ -1252,6 +1421,62 @@ private fun BasicSettingsContent(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 槽位信息按钮
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.susfs_slot_info_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.susfs_slot_info_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
lineHeight = 14.sp
|
||||
)
|
||||
|
||||
OutlinedButton(
|
||||
onClick = onShowSlotInfo,
|
||||
enabled = !isLoading,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Storage,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
stringResource(R.string.susfs_slot_info_title),
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,15 @@ object SuSFSManager {
|
||||
private const val MODULE_ID = "susfs_manager"
|
||||
private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID"
|
||||
|
||||
/**
|
||||
* 槽位信息数据类
|
||||
*/
|
||||
data class SlotInfo(
|
||||
val slotName: String,
|
||||
val uname: String,
|
||||
val buildTime: String
|
||||
)
|
||||
|
||||
private fun getSuSFS(): String {
|
||||
return try {
|
||||
getSuSFSVersion()
|
||||
@@ -86,6 +95,74 @@ object SuSFSManager {
|
||||
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令的通用方法
|
||||
*/
|
||||
private fun runCmd(shell: Shell, cmd: String): String {
|
||||
return shell.newJob()
|
||||
.add(cmd)
|
||||
.to(mutableListOf<String>(), null)
|
||||
.exec().out
|
||||
.joinToString("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前槽位信息
|
||||
*/
|
||||
suspend fun getCurrentSlotInfo(): List<SlotInfo> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val shell = getRootShell()
|
||||
val slotInfoList = mutableListOf<SlotInfo>()
|
||||
|
||||
// 获取boot_a槽位信息
|
||||
val bootAUname = runCmd(shell,
|
||||
"strings -n 20 /dev/block/by-name/boot_a | awk '/Linux version/ && ++c==2 {print $3; exit}'"
|
||||
).trim()
|
||||
val bootABuildTime = runCmd(shell, "strings -n 20 /dev/block/by-name/boot_a | sed -n '/Linux version.*#/{s/.*#/#/p;q}'").trim()
|
||||
|
||||
if (bootAUname.isNotEmpty() && bootABuildTime.isNotEmpty()) {
|
||||
val uname = bootAUname.ifEmpty { "unknown" }
|
||||
val buildTime = bootABuildTime.ifEmpty { "unknown" }
|
||||
slotInfoList.add(SlotInfo("boot_a", uname, buildTime))
|
||||
}
|
||||
|
||||
// 获取boot_b槽位信息
|
||||
val bootBUname = runCmd(shell,
|
||||
"strings -n 20 /dev/block/by-name/boot_b | awk '/Linux version/ && ++c==2 {print $3; exit}'"
|
||||
).trim()
|
||||
val bootBBuildTime = runCmd(shell, "strings -n 20 /dev/block/by-name/boot_b | sed -n '/Linux version.*#/{s/.*#/#/p;q}'").trim()
|
||||
|
||||
if (bootBUname.isNotEmpty() && bootBBuildTime.isNotEmpty()) {
|
||||
val uname = bootBUname.ifEmpty { "unknown" }
|
||||
val buildTime = bootBBuildTime.ifEmpty { "unknown" }
|
||||
slotInfoList.add(SlotInfo("boot_b", uname, buildTime))
|
||||
}
|
||||
|
||||
slotInfoList
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活动槽位
|
||||
*/
|
||||
suspend fun getCurrentActiveSlot(): String = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val shell = getRootShell()
|
||||
val suffix = runCmd(shell, "getprop ro.boot.slot_suffix").trim()
|
||||
when (suffix) {
|
||||
"_a" -> "boot_a"
|
||||
"_b" -> "boot_b"
|
||||
else -> "unknown"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存uname值
|
||||
*/
|
||||
@@ -884,7 +961,7 @@ object SuSFSManager {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"SuSFS self-startup module is enabled, module path:$MODULE_PATH",
|
||||
context.getString(R.string.susfs_autostart_enabled_success, MODULE_PATH),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
@@ -906,7 +983,7 @@ object SuSFSManager {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"SuSFS自启动模块已禁用",
|
||||
context.getString(R.string.susfs_autostart_disabled_success),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.ramcosta.composedestinations.generated.destinations.SuSFSConfigScreenDestination
|
||||
|
||||
/**
|
||||
* @author ShirkNeko
|
||||
@@ -1158,21 +1157,6 @@ fun MoreSettingsScreen(
|
||||
}
|
||||
)
|
||||
|
||||
// SuSFS 配置(仅在支持时显示)
|
||||
if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) {
|
||||
SettingItem(
|
||||
icon = Icons.Default.Settings,
|
||||
title = stringResource(R.string.susfs_config_setting_title),
|
||||
subtitle = stringResource(
|
||||
R.string.susfs_config_setting_summary,
|
||||
SuSFSManager.getUnameValue(context)
|
||||
),
|
||||
onClick = {
|
||||
navigator.navigate(SuSFSConfigScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// SuSFS 开关(仅在支持时显示)
|
||||
val suSFS = getSuSFS()
|
||||
val isSUS_SU = getSuSFSFeatures()
|
||||
|
||||
@@ -529,4 +529,18 @@
|
||||
<string name="susfs_execution_location_post_fs_data">Post-FS-Data</string>
|
||||
<string name="susfs_execution_location_service_description">在系统服务启动后执行</string>
|
||||
<string name="susfs_execution_location_post_fs_data_description">在文件系统挂载后但系统完全启动前执行,可能会导致循环重启</string>
|
||||
<string name="susfs_slot_info_title">槽位信息</string>
|
||||
<string name="susfs_slot_info_description">查看当前启动槽位信息并复制数值</string>
|
||||
<string name="susfs_current_active_slot">当前活动槽位:%s</string>
|
||||
<string name="susfs_slot_name">槽位:%s</string>
|
||||
<string name="susfs_slot_uname">Uname:%s</string>
|
||||
<string name="susfs_slot_build_time">构建时间:%s</string>
|
||||
<string name="susfs_slot_current_badge">当前</string>
|
||||
<string name="susfs_slot_use_uname">使用Uname</string>
|
||||
<string name="susfs_slot_use_build_time">使用构建时间</string>
|
||||
<string name="susfs_slot_info_unavailable">无法获取槽位信息</string>
|
||||
<string name="susfs_slot_info_loading">正在加载槽位信息…</string>
|
||||
<!-- SuSFS 自启动相关字符串 -->
|
||||
<string name="susfs_autostart_enabled_success">SuSFS自启动模块已启用,模块路径:%s</string>
|
||||
<string name="susfs_autostart_disabled_success">SuSFS自启动模块已禁用</string>
|
||||
</resources>
|
||||
|
||||
@@ -531,4 +531,18 @@
|
||||
<string name="susfs_execution_location_post_fs_data">Post-FS-Data</string>
|
||||
<string name="susfs_execution_location_service_description">Execute after system services start</string>
|
||||
<string name="susfs_execution_location_post_fs_data_description">Execute after file system is mounted but before system is fully booted,May cause a boot loop</string>
|
||||
<string name="susfs_slot_info_title">Slot Information</string>
|
||||
<string name="susfs_slot_info_description">View current boot slot information and copy values</string>
|
||||
<string name="susfs_current_active_slot">Current Active Slot: %s</string>
|
||||
<string name="susfs_slot_name">Slot: %s</string>
|
||||
<string name="susfs_slot_uname">Uname: %s</string>
|
||||
<string name="susfs_slot_build_time">Build Time: %s</string>
|
||||
<string name="susfs_slot_current_badge">Current</string>
|
||||
<string name="susfs_slot_use_uname">Use Uname</string>
|
||||
<string name="susfs_slot_use_build_time">Use Build Time</string>
|
||||
<string name="susfs_slot_info_unavailable">Unable to retrieve slot information</string>
|
||||
<string name="susfs_slot_info_loading">Loading slot information…</string>
|
||||
<!-- SuSFS 自启动相关字符串 -->
|
||||
<string name="susfs_autostart_enabled_success">SuSFS auto-start module enabled, module path: %s</string>
|
||||
<string name="susfs_autostart_disabled_success">SuSFS auto-start module disabled</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user