From 97e367aa92269bd1bc5ce27d990e81b2d5593df7 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sat, 14 Jun 2025 20:00:16 +0800 Subject: [PATCH] manager: Update and add SuSFS related settings and functions. --- .../ultra/ui/component/SuSFSConfigDialog.kt | 1495 +++++++++++++++-- .../com/sukisu/ultra/ui/util/SuSFSManager.kt | 516 +++++- .../zako/zako/zakoui/screen/MoreSettings.kt | 183 +- .../src/main/res/values-zh-rCN/strings.xml | 85 +- manager/app/src/main/res/values/strings.xml | 99 +- 5 files changed, 2090 insertions(+), 288 deletions(-) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialog.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialog.kt index 0c77dbb0..4cbe3403 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialog.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialog.kt @@ -1,33 +1,59 @@ package com.sukisu.ultra.ui.component +import android.annotation.SuppressLint import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.AutoMode +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Folder +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.PlayArrow +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 import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.Switch +import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -37,7 +63,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.sukisu.ultra.R import com.sukisu.ultra.ui.util.SuSFSManager import kotlinx.coroutines.launch @@ -45,6 +73,8 @@ import kotlinx.coroutines.launch /** * SuSFS配置对话框 */ +@SuppressLint("SdCardPath") +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SuSFSConfigDialog( onDismiss: () -> Unit @@ -52,16 +82,63 @@ fun SuSFSConfigDialog( val context = LocalContext.current val coroutineScope = rememberCoroutineScope() + var selectedTabIndex by remember { mutableIntStateOf(0) } var unameValue by remember { mutableStateOf("") } var isLoading by remember { mutableStateOf(false) } var showConfirmReset by remember { mutableStateOf(false) } var autoStartEnabled by remember { mutableStateOf(false) } var lastAppliedValue by remember { mutableStateOf("") } + // 路径管理相关状态 + var susPaths by remember { mutableStateOf(emptySet()) } + var susMounts by remember { mutableStateOf(emptySet()) } + var tryUmounts by remember { mutableStateOf(emptySet()) } + var androidDataPath by remember { mutableStateOf("") } + var sdcardPath by remember { mutableStateOf("") } + + // 启用功能状态相关 + var enabledFeatures by remember { mutableStateOf(emptyList()) } + var isLoadingFeatures by remember { mutableStateOf(false) } + + // 添加路径对话框状态 + var showAddPathDialog by remember { mutableStateOf(false) } + var showAddMountDialog by remember { mutableStateOf(false) } + var showAddUmountDialog by remember { mutableStateOf(false) } + var showRunUmountDialog by remember { mutableStateOf(false) } + var newPath by remember { mutableStateOf("") } + var newMount by remember { mutableStateOf("") } + var newUmountPath by remember { mutableStateOf("") } + var newUmountMode by remember { mutableIntStateOf(0) } + var umountModeExpanded by remember { mutableStateOf(false) } + + // 重置确认对话框状态 + var showResetPathsDialog by remember { mutableStateOf(false) } + var showResetMountsDialog by remember { mutableStateOf(false) } + var showResetUmountsDialog by remember { mutableStateOf(false) } + + val tabTitles = listOf( + stringResource(R.string.susfs_tab_basic_settings), + stringResource(R.string.susfs_tab_sus_paths), + stringResource(R.string.susfs_tab_sus_mounts), + stringResource(R.string.susfs_tab_try_umount), + stringResource(R.string.susfs_tab_path_settings), + stringResource(R.string.susfs_tab_enabled_features) + ) + // 实时判断是否可以启用开机自启动 val canEnableAutoStart by remember { derivedStateOf { - unameValue.trim().isNotBlank() && unameValue.trim() != "default" + unameValue.trim().isNotBlank() && unameValue.trim() != "default" || + susPaths.isNotEmpty() || susMounts.isNotEmpty() || tryUmounts.isNotEmpty() + } + } + + // 加载启用功能状态 + fun loadEnabledFeatures() { + coroutineScope.launch { + isLoadingFeatures = true + enabledFeatures = SuSFSManager.getEnabledFeatures(context) + isLoadingFeatures = false } } @@ -70,17 +147,425 @@ fun SuSFSConfigDialog( unameValue = SuSFSManager.getUnameValue(context) autoStartEnabled = SuSFSManager.isAutoStartEnabled(context) lastAppliedValue = SuSFSManager.getLastAppliedValue(context) + susPaths = SuSFSManager.getSusPaths(context) + susMounts = SuSFSManager.getSusMounts(context) + tryUmounts = SuSFSManager.getTryUmounts(context) + androidDataPath = SuSFSManager.getAndroidDataPath(context) + sdcardPath = SuSFSManager.getSdcardPath(context) + } + + // 当切换到启用功能状态标签页时加载数据 + LaunchedEffect(selectedTabIndex) { + if (selectedTabIndex == 5) { + loadEnabledFeatures() + } } // 当输入值变化时,自动调整开机自启动状态 LaunchedEffect(canEnableAutoStart) { if (!canEnableAutoStart && autoStartEnabled) { - // 如果输入值变为default或空,自动关闭开机自启动 autoStartEnabled = false SuSFSManager.configureAutoStart(context, false) } } + // 添加路径对话框 + if (showAddPathDialog) { + AlertDialog( + onDismissRequest = { showAddPathDialog = false }, + title = { + Text( + stringResource(R.string.susfs_add_sus_path), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + }, + text = { + OutlinedTextField( + value = newPath, + onValueChange = { newPath = it }, + label = { Text(stringResource(R.string.susfs_path_label)) }, + placeholder = { Text(stringResource(R.string.susfs_path_placeholder)) }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp) + ) + }, + confirmButton = { + Button( + onClick = { + if (newPath.isNotBlank()) { + coroutineScope.launch { + isLoading = true + if (SuSFSManager.addSusPath(context, newPath.trim())) { + susPaths = SuSFSManager.getSusPaths(context) + } + isLoading = false + newPath = "" + showAddPathDialog = false + } + } + }, + enabled = newPath.isNotBlank() && !isLoading, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_add)) + } + }, + dismissButton = { + TextButton( + onClick = { + showAddPathDialog = false + newPath = "" + }, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.cancel)) + } + }, + shape = RoundedCornerShape(16.dp) + ) + } + + // 添加挂载对话框 + if (showAddMountDialog) { + AlertDialog( + onDismissRequest = { showAddMountDialog = false }, + title = { + Text( + stringResource(R.string.susfs_add_sus_mount), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + }, + text = { + OutlinedTextField( + value = newMount, + onValueChange = { newMount = it }, + label = { Text(stringResource(R.string.susfs_mount_path_label)) }, + placeholder = { Text(stringResource(R.string.susfs_path_placeholder)) }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp) + ) + }, + confirmButton = { + Button( + onClick = { + if (newMount.isNotBlank()) { + coroutineScope.launch { + isLoading = true + if (SuSFSManager.addSusMount(context, newMount.trim())) { + susMounts = SuSFSManager.getSusMounts(context) + } + isLoading = false + newMount = "" + showAddMountDialog = false + } + } + }, + enabled = newMount.isNotBlank() && !isLoading, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_add)) + } + }, + dismissButton = { + TextButton( + onClick = { + showAddMountDialog = false + newMount = "" + }, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.cancel)) + } + }, + shape = RoundedCornerShape(16.dp) + ) + } + + // 添加尝试卸载对话框 + if (showAddUmountDialog) { + AlertDialog( + onDismissRequest = { showAddUmountDialog = false }, + title = { + Text( + stringResource(R.string.susfs_add_try_umount), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + }, + text = { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + OutlinedTextField( + value = newUmountPath, + onValueChange = { newUmountPath = it }, + label = { Text(stringResource(R.string.susfs_path_label)) }, + placeholder = { Text(stringResource(R.string.susfs_path_placeholder)) }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp) + ) + + ExposedDropdownMenuBox( + expanded = umountModeExpanded, + onExpandedChange = { umountModeExpanded = !umountModeExpanded } + ) { + OutlinedTextField( + value = if (newUmountMode == 0) + stringResource(R.string.susfs_umount_mode_normal) + else + stringResource(R.string.susfs_umount_mode_detach), + onValueChange = { }, + readOnly = true, + label = { Text(stringResource(R.string.susfs_umount_mode_label)) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) }, + modifier = Modifier + .fillMaxWidth() + .menuAnchor(MenuAnchorType.PrimaryEditable, true), + shape = RoundedCornerShape(8.dp) + ) + ExposedDropdownMenu( + expanded = umountModeExpanded, + onDismissRequest = { umountModeExpanded = false } + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.susfs_umount_mode_normal)) }, + onClick = { + newUmountMode = 0 + umountModeExpanded = false + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.susfs_umount_mode_detach)) }, + onClick = { + newUmountMode = 1 + umountModeExpanded = false + } + ) + } + } + } + }, + confirmButton = { + Button( + onClick = { + if (newUmountPath.isNotBlank()) { + coroutineScope.launch { + isLoading = true + if (SuSFSManager.addTryUmount(context, newUmountPath.trim(), newUmountMode)) { + tryUmounts = SuSFSManager.getTryUmounts(context) + } + isLoading = false + newUmountPath = "" + newUmountMode = 0 + showAddUmountDialog = false + } + } + }, + enabled = newUmountPath.isNotBlank() && !isLoading, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_add)) + } + }, + dismissButton = { + TextButton( + onClick = { + showAddUmountDialog = false + newUmountPath = "" + newUmountMode = 0 + }, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.cancel)) + } + }, + shape = RoundedCornerShape(16.dp) + ) + } + + // 运行尝试卸载确认对话框 + if (showRunUmountDialog) { + AlertDialog( + onDismissRequest = { showRunUmountDialog = false }, + title = { + Text( + stringResource(R.string.susfs_run_umount_confirm_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + }, + text = { Text(stringResource(R.string.susfs_run_umount_confirm_message)) }, + confirmButton = { + Button( + onClick = { + coroutineScope.launch { + isLoading = true + SuSFSManager.runTryUmount(context) + isLoading = false + showRunUmountDialog = false + } + }, + enabled = !isLoading, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.confirm)) + } + }, + dismissButton = { + TextButton( + onClick = { showRunUmountDialog = false }, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.cancel)) + } + }, + shape = RoundedCornerShape(16.dp) + ) + } + + // 重置SUS路径确认对话框 + if (showResetPathsDialog) { + AlertDialog( + onDismissRequest = { showResetPathsDialog = false }, + title = { + Text( + stringResource(R.string.susfs_reset_paths_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + }, + text = { Text(stringResource(R.string.susfs_reset_paths_message)) }, + confirmButton = { + Button( + onClick = { + coroutineScope.launch { + isLoading = true + SuSFSManager.saveSusPaths(context, emptySet()) + susPaths = emptySet() + if (SuSFSManager.isAutoStartEnabled(context)) { + SuSFSManager.configureAutoStart(context, true) + } + isLoading = false + showResetPathsDialog = false + } + }, + enabled = !isLoading, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error + ), + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_reset_confirm)) + } + }, + dismissButton = { + TextButton( + onClick = { showResetPathsDialog = false }, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.cancel)) + } + }, + shape = RoundedCornerShape(16.dp) + ) + } + + // 重置SUS挂载确认对话框 + if (showResetMountsDialog) { + AlertDialog( + onDismissRequest = { showResetMountsDialog = false }, + title = { + Text( + stringResource(R.string.susfs_reset_mounts_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + }, + text = { Text(stringResource(R.string.susfs_reset_mounts_message)) }, + confirmButton = { + Button( + onClick = { + coroutineScope.launch { + isLoading = true + SuSFSManager.saveSusMounts(context, emptySet()) + susMounts = emptySet() + if (SuSFSManager.isAutoStartEnabled(context)) { + SuSFSManager.configureAutoStart(context, true) + } + isLoading = false + showResetMountsDialog = false + } + }, + enabled = !isLoading, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error + ), + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_reset_confirm)) + } + }, + dismissButton = { + TextButton( + onClick = { showResetMountsDialog = false }, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.cancel)) + } + }, + shape = RoundedCornerShape(16.dp) + ) + } + + // 重置尝试卸载确认对话框 + if (showResetUmountsDialog) { + AlertDialog( + onDismissRequest = { showResetUmountsDialog = false }, + title = { + Text( + stringResource(R.string.susfs_reset_umounts_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + }, + text = { Text(stringResource(R.string.susfs_reset_umounts_message)) }, + confirmButton = { + Button( + onClick = { + coroutineScope.launch { + isLoading = true + SuSFSManager.saveTryUmounts(context, emptySet()) + tryUmounts = emptySet() + if (SuSFSManager.isAutoStartEnabled(context)) { + SuSFSManager.configureAutoStart(context, true) + } + isLoading = false + showResetUmountsDialog = false + } + }, + enabled = !isLoading, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error + ), + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_reset_confirm)) + } + }, + dismissButton = { + TextButton( + onClick = { showResetUmountsDialog = false }, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.cancel)) + } + }, + shape = RoundedCornerShape(16.dp) + ) + } + // 重置确认对话框 if (showConfirmReset) { AlertDialog( @@ -88,14 +573,12 @@ fun SuSFSConfigDialog( title = { Text( text = stringResource(R.string.susfs_reset_confirm_title), - style = MaterialTheme.typography.titleMedium + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) }, - text = { - Text(stringResource(R.string.susfs_reset_confirm_message)) - }, confirmButton = { - TextButton( + Button( onClick = { showConfirmReset = false coroutineScope.launch { @@ -107,18 +590,24 @@ fun SuSFSConfigDialog( } isLoading = false } - } + }, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error + ), + shape = RoundedCornerShape(8.dp) ) { Text(stringResource(R.string.susfs_reset_confirm)) } }, dismissButton = { TextButton( - onClick = { showConfirmReset = false } + onClick = { showConfirmReset = false }, + shape = RoundedCornerShape(8.dp) ) { Text(stringResource(R.string.cancel)) } - } + }, + shape = RoundedCornerShape(16.dp) ) } @@ -131,9 +620,10 @@ fun SuSFSConfigDialog( Icon( imageVector = Icons.Default.Settings, contentDescription = null, - tint = MaterialTheme.colorScheme.primary + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(24.dp) ) - Spacer(modifier = Modifier.width(8.dp)) + Spacer(modifier = Modifier.width(12.dp)) Text( text = stringResource(R.string.susfs_config_title), style = MaterialTheme.typography.titleLarge, @@ -145,183 +635,866 @@ fun SuSFSConfigDialog( Column( modifier = Modifier.fillMaxWidth() ) { - // 说明卡片 - Card( + // 优化后的标签页 - 使用ScrollableTabRow + ScrollableTabRow( + selectedTabIndex = selectedTabIndex, + edgePadding = 16.dp, modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) - ), - shape = RoundedCornerShape(8.dp) + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface ) { - Column( - modifier = Modifier.padding(12.dp) - ) { - Text( - text = stringResource(R.string.susfs_config_description), - style = MaterialTheme.typography.titleSmall, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.susfs_config_description_text), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant + tabTitles.forEachIndexed { index, title -> + Tab( + selected = selectedTabIndex == index, + onClick = { selectedTabIndex = index }, + text = { + Text( + text = title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 13.sp, + fontWeight = if (selectedTabIndex == index) FontWeight.Bold else FontWeight.Normal + ) + }, + modifier = Modifier.padding(horizontal = 4.dp) ) } } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(20.dp)) - // 输入框 - OutlinedTextField( - value = unameValue, - onValueChange = { unameValue = it }, - label = { Text(stringResource(R.string.susfs_uname_label)) }, - placeholder = { Text(stringResource(R.string.susfs_uname_placeholder)) }, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading, - singleLine = true - ) - - Spacer(modifier = Modifier.height(8.dp)) - - // 当前值显示 - Text( - text = stringResource(R.string.susfs_current_value, SuSFSManager.getUnameValue(context)), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - - Spacer(modifier = Modifier.height(16.dp)) - - // 开机自启动开关 - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors( - containerColor = if (canEnableAutoStart) { - MaterialTheme.colorScheme.surface - } else { - MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f) - } - ), - shape = RoundedCornerShape(8.dp) + // 标签页内容 + Box( + modifier = Modifier + .fillMaxWidth() + .height(380.dp) ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier.weight(1f) - ) { - Row( - verticalAlignment = Alignment.CenterVertically + when (selectedTabIndex) { + 0 -> { + // 基本设置 + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Icon( - imageVector = Icons.Default.AutoMode, - contentDescription = null, - tint = if (canEnableAutoStart) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) - }, - modifier = Modifier.padding(end = 8.dp) - ) - Text( - text = stringResource(R.string.susfs_autostart_title), - style = MaterialTheme.typography.titleSmall, - fontWeight = FontWeight.Medium, - color = if (canEnableAutoStart) { - MaterialTheme.colorScheme.onSurface - } else { - MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) - } - ) - } - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = if (canEnableAutoStart) { - stringResource(R.string.susfs_autostart_description) - } else { - stringResource(R.string.susfs_autostart_tis) - }, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy( - alpha = if (canEnableAutoStart) 1f else 0.5f - ) - ) - } - Switch( - checked = autoStartEnabled, - onCheckedChange = { enabled -> - if (canEnableAutoStart) { - coroutineScope.launch { - isLoading = true - if (SuSFSManager.configureAutoStart(context, enabled)) { - autoStartEnabled = enabled - } - isLoading = false + // 说明卡片 + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f) + ), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier.padding(14.dp) + ) { + Text( + text = stringResource(R.string.susfs_config_description), + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = stringResource(R.string.susfs_config_description_text), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = 16.sp + ) } } - }, - enabled = !isLoading && canEnableAutoStart - ) + + // 输入框 + OutlinedTextField( + value = unameValue, + onValueChange = { unameValue = it }, + label = { Text(stringResource(R.string.susfs_uname_label)) }, + placeholder = { Text(stringResource(R.string.susfs_uname_placeholder)) }, + modifier = Modifier.fillMaxWidth(), + enabled = !isLoading, + singleLine = true, + shape = RoundedCornerShape(8.dp) + ) + + // 当前值显示 + Text( + text = stringResource(R.string.susfs_current_value, SuSFSManager.getUnameValue(context)), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + // 开机自启动开关 + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = if (canEnableAutoStart) { + MaterialTheme.colorScheme.surface + } else { + MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f) + } + ), + shape = RoundedCornerShape(12.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(14.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier.weight(1f) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.AutoMode, + contentDescription = null, + tint = if (canEnableAutoStart) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) + }, + modifier = Modifier + .size(18.dp) + .padding(end = 8.dp) + ) + Text( + text = stringResource(R.string.susfs_autostart_title), + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.Medium, + color = if (canEnableAutoStart) { + MaterialTheme.colorScheme.onSurface + } else { + MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) + } + ) + } + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = if (canEnableAutoStart) { + stringResource(R.string.susfs_autostart_description) + } else { + stringResource(R.string.susfs_autostart_requirement) + }, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy( + alpha = if (canEnableAutoStart) 1f else 0.5f + ), + lineHeight = 14.sp + ) + } + Switch( + checked = autoStartEnabled, + onCheckedChange = { enabled -> + if (canEnableAutoStart) { + coroutineScope.launch { + isLoading = true + if (SuSFSManager.configureAutoStart(context, enabled)) { + autoStartEnabled = enabled + } + isLoading = false + } + } + }, + enabled = !isLoading && canEnableAutoStart + ) + } + } + + // 重置按钮 + ResetInfoCard( + tabName = stringResource(R.string.susfs_tab_basic_settings), + onResetClick = { showConfirmReset = true }, + enabled = !isLoading + ) + } + } + 1 -> { + // SUS路径 + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + UnifiedButtonRow( + primaryButton = { + FloatingActionButton( + onClick = { showAddPathDialog = true }, + modifier = Modifier.size(48.dp), + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = stringResource(R.string.susfs_add_button_description), + modifier = Modifier.size(24.dp) + ) + } + }, + secondaryButtons = { + Text( + text = stringResource(R.string.susfs_sus_paths_management), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + ) + + if (susPaths.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_paths_configured) + ) + } else { + LazyColumn( + modifier = Modifier.height(180.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(susPaths.toList()) { path -> + PathItemCard( + path = path, + icon = Icons.Default.Folder, + onDelete = { + coroutineScope.launch { + isLoading = true + if (SuSFSManager.removeSusPath(context, path)) { + susPaths = SuSFSManager.getSusPaths(context) + } + isLoading = false + } + }, + isLoading = isLoading + ) + } + } + } + + ResetInfoCard( + tabName = stringResource(R.string.susfs_tab_sus_paths), + onResetClick = { showResetPathsDialog = true }, + enabled = !isLoading && susPaths.isNotEmpty() + ) + } + } + 2 -> { + // SUS挂载 + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + UnifiedButtonRow( + primaryButton = { + FloatingActionButton( + onClick = { showAddMountDialog = true }, + modifier = Modifier.size(48.dp), + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = stringResource(R.string.susfs_add_button_description), + modifier = Modifier.size(24.dp) + ) + } + }, + secondaryButtons = { + Text( + text = stringResource(R.string.susfs_sus_mounts_management), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + ) + + if (susMounts.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_mounts_configured) + ) + } else { + LazyColumn( + modifier = Modifier.height(180.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(susMounts.toList()) { mount -> + PathItemCard( + path = mount, + icon = Icons.Default.Storage, + onDelete = { + coroutineScope.launch { + isLoading = true + if (SuSFSManager.removeSusMount(context, mount)) { + susMounts = SuSFSManager.getSusMounts(context) + } + isLoading = false + } + }, + isLoading = isLoading + ) + } + } + } + + ResetInfoCard( + tabName = stringResource(R.string.susfs_tab_sus_mounts), + onResetClick = { showResetMountsDialog = true }, + enabled = !isLoading && susMounts.isNotEmpty() + ) + } + } + 3 -> { + // 尝试卸载 + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + UnifiedButtonRow( + primaryButton = { + FloatingActionButton( + onClick = { showAddUmountDialog = true }, + modifier = Modifier.size(48.dp), + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = stringResource(R.string.susfs_add_button_description), + modifier = Modifier.size(24.dp) + ) + } + }, + secondaryButtons = { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.susfs_try_umount_management), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + if (tryUmounts.isNotEmpty()) { + FloatingActionButton( + onClick = { showRunUmountDialog = true }, + modifier = Modifier.size(40.dp), + containerColor = MaterialTheme.colorScheme.secondary, + contentColor = MaterialTheme.colorScheme.onSecondary + ) { + Icon( + imageVector = Icons.Default.PlayArrow, + contentDescription = stringResource(R.string.susfs_run_button_description), + modifier = Modifier.size(20.dp) + ) + } + } + } + } + ) + + if (tryUmounts.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_umounts_configured) + ) + } else { + LazyColumn( + modifier = Modifier.height(180.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(tryUmounts.toList()) { umountEntry -> + val parts = umountEntry.split("|") + val path = if (parts.isNotEmpty()) parts[0] else umountEntry + val mode = if (parts.size > 1) parts[1] else "0" + val modeText = if (mode == "0") + stringResource(R.string.susfs_umount_mode_normal_short) + else + stringResource(R.string.susfs_umount_mode_detach_short) + + PathItemCard( + path = path, + icon = Icons.Default.Storage, + additionalInfo = stringResource(R.string.susfs_umount_mode_display, modeText, mode), + onDelete = { + coroutineScope.launch { + isLoading = true + if (SuSFSManager.removeTryUmount(context, umountEntry)) { + tryUmounts = SuSFSManager.getTryUmounts(context) + } + isLoading = false + } + }, + isLoading = isLoading + ) + } + } + } + + ResetInfoCard( + tabName = stringResource(R.string.susfs_tab_try_umount), + onResetClick = { showResetUmountsDialog = true }, + enabled = !isLoading && tryUmounts.isNotEmpty() + ) + } + } + 4 -> { + // 路径设置 + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + Text( + text = stringResource(R.string.susfs_path_settings), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + + // Android Data路径设置 + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier.padding(14.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + OutlinedTextField( + value = androidDataPath, + onValueChange = { androidDataPath = it }, + label = { Text(stringResource(R.string.susfs_android_data_path_label)) }, + placeholder = { Text("/sdcard/Android/data") }, + modifier = Modifier.fillMaxWidth(), + enabled = !isLoading, + singleLine = true, + shape = RoundedCornerShape(8.dp) + ) + + Button( + onClick = { + coroutineScope.launch { + isLoading = true + SuSFSManager.setAndroidDataPath(context, androidDataPath.trim()) + isLoading = false + } + }, + enabled = !isLoading && androidDataPath.isNotBlank(), + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_set_android_data_path)) + } + } + } + + // SD卡路径设置 + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier.padding(14.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + OutlinedTextField( + value = sdcardPath, + onValueChange = { sdcardPath = it }, + label = { Text(stringResource(R.string.susfs_sdcard_path_label)) }, + placeholder = { Text("/sdcard") }, + modifier = Modifier.fillMaxWidth(), + enabled = !isLoading, + singleLine = true, + shape = RoundedCornerShape(8.dp) + ) + + Button( + onClick = { + coroutineScope.launch { + isLoading = true + SuSFSManager.setSdcardPath(context, sdcardPath.trim()) + isLoading = false + } + }, + enabled = !isLoading && sdcardPath.isNotBlank(), + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_set_sdcard_path)) + } + } + } + + ResetInfoCard( + tabName = stringResource(R.string.susfs_tab_path_settings), + onResetClick = { + androidDataPath = "/sdcard/Android/data" + sdcardPath = "/sdcard" + coroutineScope.launch { + isLoading = true + SuSFSManager.setAndroidDataPath(context, androidDataPath) + SuSFSManager.setSdcardPath(context, sdcardPath) + isLoading = false + } + }, + enabled = !isLoading + ) + } + } + 5 -> { + // 启用功能状态 + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + UnifiedButtonRow( + primaryButton = { + FloatingActionButton( + onClick = { loadEnabledFeatures() }, + modifier = Modifier.size(48.dp), + containerColor = MaterialTheme.colorScheme.secondary, + contentColor = MaterialTheme.colorScheme.onSecondary + ) { + Icon( + imageVector = Icons.Default.Refresh, + contentDescription = stringResource(R.string.susfs_refresh_button_description), + modifier = Modifier.size(24.dp) + ) + } + }, + secondaryButtons = { + Text( + text = stringResource(R.string.susfs_enabled_features_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + ) + + // 说明卡片 + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f) + ), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier.padding(14.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .size(18.dp) + .padding(end = 8.dp) + ) + Text( + text = stringResource(R.string.susfs_enabled_features_description), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = 16.sp + ) + } + } + } + + if (isLoadingFeatures) { + EmptyStateCard( + message = stringResource(R.string.refresh) + ) + } else if (enabledFeatures.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_features_found) + ) + } else { + LazyColumn( + modifier = Modifier.height(240.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(enabledFeatures) { feature -> + FeatureStatusCard(feature = feature) + } + } + } + } + } } } - - Spacer(modifier = Modifier.height(16.dp)) - - // 重置按钮 - OutlinedButton( - onClick = { showConfirmReset = true }, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading - ) { - Icon( - imageVector = Icons.Default.RestoreFromTrash, - contentDescription = null, - modifier = Modifier.padding(end = 8.dp) - ) - Text(stringResource(R.string.susfs_reset_to_default)) - } } }, confirmButton = { Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { TextButton( onClick = onDismiss, - enabled = !isLoading + enabled = !isLoading, + shape = RoundedCornerShape(8.dp) ) { Text(stringResource(R.string.cancel)) } - Button( - onClick = { - if (unameValue.isNotBlank()) { - coroutineScope.launch { - isLoading = true - val success = SuSFSManager.setUname(context, unameValue.trim()) - if (success) { - lastAppliedValue = unameValue.trim() - onDismiss() + if (selectedTabIndex == 0) { + Button( + onClick = { + if (unameValue.isNotBlank()) { + coroutineScope.launch { + isLoading = true + val success = SuSFSManager.setUname(context, unameValue.trim()) + if (success) { + lastAppliedValue = unameValue.trim() + onDismiss() + } + isLoading = false } - isLoading = false } - } - }, - enabled = !isLoading && unameValue.isNotBlank() - ) { - Text( - stringResource(R.string.susfs_apply) - ) + }, + enabled = !isLoading && unameValue.isNotBlank(), + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_apply)) + } + } else { + Button( + onClick = onDismiss, + enabled = !isLoading, + shape = RoundedCornerShape(8.dp) + ) { + Text(stringResource(R.string.susfs_done)) + } } } }, - dismissButton = null + dismissButton = null, + shape = RoundedCornerShape(20.dp) ) +} + + +/** + * 统一的按钮布局组件 + */ +@Composable +private fun UnifiedButtonRow( + primaryButton: @Composable () -> Unit, + secondaryButtons: @Composable () -> Unit = {}, + @SuppressLint("ModifierParameter") modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + secondaryButtons() + } + primaryButton() + } +} + +/** + * 重置功能说明卡片组件 + */ +@Composable +private fun ResetInfoCard( + tabName: String, + onResetClick: () -> Unit, + enabled: Boolean = true, + @SuppressLint("ModifierParameter") modifier: Modifier = Modifier +) { + Card( + modifier = modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f) + ), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedButton( + onClick = onResetClick, + enabled = enabled, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp) + ) { + Icon( + imageVector = Icons.Default.RestoreFromTrash, + contentDescription = stringResource(R.string.susfs_reset_section_description), + modifier = Modifier + .size(16.dp) + .padding(end = 8.dp) + ) + Text(stringResource(R.string.susfs_reset, tabName)) + } + } + } + } +} + +/** + * 空状态显示组件 + */ +@Composable +private fun EmptyStateCard( + message: String, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.2f) + ), + shape = RoundedCornerShape(12.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = message, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + } + } +} + +/** + * 路径项目卡片组件 + */ +@Composable +private fun PathItemCard( + path: String, + icon: androidx.compose.ui.graphics.vector.ImageVector, + onDelete: () -> Unit, + isLoading: Boolean = false, + additionalInfo: String? = null +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 2.dp), + shape = RoundedCornerShape(8.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.weight(1f) + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .size(18.dp) + .padding(end = 10.dp) + ) + Column { + Text( + text = path, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium + ) + if (additionalInfo != null) { + Text( + text = additionalInfo, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + IconButton( + onClick = onDelete, + enabled = !isLoading, + modifier = Modifier.size(36.dp) + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = stringResource(R.string.susfs_delete_button_description), + tint = MaterialTheme.colorScheme.error, + modifier = Modifier.size(18.dp) + ) + } + } + } +} + +/** + * 启用功能状态卡片组件 + */ +@Composable +private fun FeatureStatusCard( + feature: SuSFSManager.EnabledFeature, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 2.dp), + shape = RoundedCornerShape(8.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 1.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.weight(1f) + ) { + Text( + text = feature.name, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium, + modifier = Modifier.weight(1f) + ) + } + Text( + text = if (feature.isEnabled) + stringResource(R.string.susfs_feature_enabled) + else + stringResource(R.string.susfs_feature_disabled), + style = MaterialTheme.typography.bodySmall, + color = if (feature.isEnabled) + MaterialTheme.colorScheme.primary + else + MaterialTheme.colorScheme.error, + fontWeight = FontWeight.Medium + ) + } + } } \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt index a829bfa3..0cde8bb4 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt @@ -1,5 +1,6 @@ package com.sukisu.ultra.ui.util +import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences import android.widget.Toast @@ -21,11 +22,43 @@ object SuSFSManager { private const val KEY_IS_ENABLED = "is_enabled" private const val KEY_AUTO_START_ENABLED = "auto_start_enabled" private const val KEY_LAST_APPLIED_VALUE = "last_applied_value" + private const val KEY_SUS_PATHS = "sus_paths" + private const val KEY_SUS_MOUNTS = "sus_mounts" + private const val KEY_TRY_UMOUNTS = "try_umounts" + private const val KEY_ANDROID_DATA_PATH = "android_data_path" + private const val KEY_SDCARD_PATH = "sdcard_path" private const val SUSFS_BINARY_NAME = "ksu_susfs" private const val DEFAULT_UNAME = "default" private const val STARTUP_SCRIPT_PATH = "/data/adb/service.d/susfs_startup.sh" private const val SUSFS_TARGET_PATH = "/data/adb/ksu/bin/$SUSFS_BINARY_NAME" + /** + * 启用功能状态数据类 + */ + data class EnabledFeature( + val name: String, + val isEnabled: Boolean + ) + + /** + * 功能配置映射数据类 + */ + private data class FeatureMapping( + val id: String, + val config: String + ) + + /** + * 执行Shell命令并返回输出 + */ + private fun runCmd(shell: Shell, cmd: String): String { + return shell.newJob() + .add(cmd) + .to(mutableListOf(), null) + .exec().out + .joinToString("\n") + } + /** * 获取Root Shell实例 */ @@ -101,6 +134,93 @@ object SuSFSManager { return getPrefs(context).getBoolean(KEY_AUTO_START_ENABLED, false) } + /** + * 保存SUS路径列表 + */ + fun saveSusPaths(context: Context, paths: Set) { + getPrefs(context).edit().apply { + putStringSet(KEY_SUS_PATHS, paths) + apply() + } + } + + /** + * 获取SUS路径列表 + */ + fun getSusPaths(context: Context): Set { + return getPrefs(context).getStringSet(KEY_SUS_PATHS, emptySet()) ?: emptySet() + } + + /** + * 保存SUS挂载列表 + */ + fun saveSusMounts(context: Context, mounts: Set) { + getPrefs(context).edit().apply { + putStringSet(KEY_SUS_MOUNTS, mounts) + apply() + } + } + + /** + * 获取SUS挂载列表 + */ + fun getSusMounts(context: Context): Set { + return getPrefs(context).getStringSet(KEY_SUS_MOUNTS, emptySet()) ?: emptySet() + } + + /** + * 保存尝试卸载列表 + */ + fun saveTryUmounts(context: Context, umounts: Set) { + getPrefs(context).edit().apply { + putStringSet(KEY_TRY_UMOUNTS, umounts) + apply() + } + } + + /** + * 获取尝试卸载列表 + */ + fun getTryUmounts(context: Context): Set { + return getPrefs(context).getStringSet(KEY_TRY_UMOUNTS, emptySet()) ?: emptySet() + } + + /** + * 保存Android Data路径 + */ + fun saveAndroidDataPath(context: Context, path: String) { + getPrefs(context).edit().apply { + putString(KEY_ANDROID_DATA_PATH, path) + apply() + } + } + + /** + * 获取Android Data路径 + */ + @SuppressLint("SdCardPath") + fun getAndroidDataPath(context: Context): String { + return getPrefs(context).getString(KEY_ANDROID_DATA_PATH, "/sdcard/Android/data") ?: "/sdcard/Android/data" + } + + /** + * 保存SD卡路径 + */ + fun saveSdcardPath(context: Context, path: String) { + getPrefs(context).edit().apply { + putString(KEY_SDCARD_PATH, path) + apply() + } + } + + /** + * 获取SD卡路径 + */ + @SuppressLint("SdCardPath") + fun getSdcardPath(context: Context): String { + return getPrefs(context).getString(KEY_SDCARD_PATH, "/sdcard") ?: "/sdcard" + } + /** * 从assets复制ksu_susfs文件到/data/adb/ksu/bin/ */ @@ -151,26 +271,81 @@ object SuSFSManager { /** * 创建开机自启动脚本 */ - private suspend fun createStartupScript(unameValue: String): Boolean = withContext(Dispatchers.IO) { + @SuppressLint("SdCardPath") + private suspend fun createStartupScript(context: Context): Boolean = withContext(Dispatchers.IO) { try { - val scriptContent = """#!/system/bin/sh -# SuSFS 开机自启动脚本 -# 由 KernelSU Manager 自动生成 + val unameValue = getUnameValue(context) + val susPaths = getSusPaths(context) + val susMounts = getSusMounts(context) + val tryUmounts = getTryUmounts(context) + val androidDataPath = getAndroidDataPath(context) + val sdcardPath = getSdcardPath(context) -# 等待系统完全启动 -sleep 30 + val scriptContent = buildString { + appendLine("#!/system/bin/sh") + appendLine("# SuSFS 开机自启动脚本") + appendLine("# 由 KernelSU Manager 自动生成") + appendLine() + appendLine("# 等待系统完全启动") + appendLine("sleep 60") + appendLine() + appendLine("# 检查二进制文件是否存在") + appendLine("if [ -f \"$SUSFS_TARGET_PATH\" ]; then") + appendLine(" # 创建日志目录") + appendLine(" mkdir -p /data/adb/ksu/log") + appendLine() -# 检查二进制文件是否存在 -if [ -f "$SUSFS_TARGET_PATH" ]; then - # 执行 SuSFS setUname 命令 - $SUSFS_TARGET_PATH set_uname '$unameValue' '$DEFAULT_UNAME' - - # 记录日志 - echo "\$(date): SuSFS setUname executed with value: $unameValue" >> /data/adb/ksu/log/susfs_startup.log -else - echo "\$(date): SuSFS binary not found at $SUSFS_TARGET_PATH" >> /data/adb/ksu/log/susfs_startup.log -fi -""" + // 设置Android Data路径 + if (androidDataPath != "/sdcard/Android/data") { + appendLine(" # 设置Android Data路径") + appendLine(" $SUSFS_TARGET_PATH set_android_data_root_path '$androidDataPath'") + appendLine(" echo \"\\$(date): Android Data路径设置为: $androidDataPath\" >> /data/adb/ksu/log/susfs_startup.log") + } + + // 设置SD卡路径 + if (sdcardPath != "/sdcard") { + appendLine(" # 设置SD卡路径") + appendLine(" $SUSFS_TARGET_PATH set_sdcard_root_path '$sdcardPath'") + appendLine(" echo \"\\$(date): SD卡路径设置为: $sdcardPath\" >> /data/adb/ksu/log/susfs_startup.log") + } + + // 添加SUS路径 + susPaths.forEach { path -> + appendLine(" # 添加SUS路径: $path") + appendLine(" $SUSFS_TARGET_PATH add_sus_path '$path'") + appendLine(" echo \"\\$(date): 添加SUS路径: $path\" >> /data/adb/ksu/log/susfs_startup.log") + } + + // 添加SUS挂载 + susMounts.forEach { mount -> + appendLine(" # 添加SUS挂载: $mount") + appendLine(" $SUSFS_TARGET_PATH add_sus_mount '$mount'") + appendLine(" echo \"\\$(date): 添加SUS挂载: $mount\" >> /data/adb/ksu/log/susfs_startup.log") + } + + // 添加尝试卸载 + tryUmounts.forEach { umount -> + val parts = umount.split("|") + if (parts.size == 2) { + val path = parts[0] + val mode = parts[1] + appendLine(" # 添加尝试卸载: $path (模式: $mode)") + appendLine(" $SUSFS_TARGET_PATH add_try_umount '$path' $mode") + appendLine(" echo \"\\$(date): 添加尝试卸载: $path (模式: $mode)\" >> /data/adb/ksu/log/susfs_startup.log") + } + } + + // 设置uname + if (unameValue != DEFAULT_UNAME) { + appendLine(" # 设置uname") + appendLine(" $SUSFS_TARGET_PATH set_uname '$unameValue' '$DEFAULT_UNAME'") + appendLine(" echo \"\\$(date): 设置uname为: $unameValue\" >> /data/adb/ksu/log/susfs_startup.log") + } + + appendLine("else") + appendLine(" echo \"\\$(date): SuSFS二进制文件未找到: $SUSFS_TARGET_PATH\" >> /data/adb/ksu/log/susfs_startup.log") + appendLine("fi") + } val shell = getRootShell() val commands = arrayOf( @@ -209,6 +384,306 @@ fi } } + /** + * 执行SuSFS命令 + */ + private suspend fun executeSusfsCommand(context: Context, command: String): Boolean = withContext(Dispatchers.IO) { + try { + // 确保二进制文件存在 + val binaryPath = copyBinaryFromAssets(context) + if (binaryPath == null) { + withContext(Dispatchers.Main) { + Toast.makeText( + context, + context.getString(R.string.susfs_binary_not_found), + Toast.LENGTH_SHORT + ).show() + } + return@withContext false + } + + // 执行命令 + val fullCommand = "$binaryPath $command" + val result = getRootShell().newJob().add(fullCommand).exec() + + if (!result.isSuccess) { + withContext(Dispatchers.Main) { + val errorOutput = result.out.joinToString("\n") + "\n" + result.err.joinToString("\n") + Toast.makeText( + context, + context.getString(R.string.susfs_command_failed) + "\n$errorOutput", + Toast.LENGTH_LONG + ).show() + } + } + + result.isSuccess + } catch (e: Exception) { + e.printStackTrace() + withContext(Dispatchers.Main) { + Toast.makeText( + context, + context.getString(R.string.susfs_command_error, e.message ?: "Unknown error"), + Toast.LENGTH_SHORT + ).show() + } + false + } + } + + /** + * 获取功能配置映射表 + */ + private fun getFeatureMappings(): List { + return listOf( + FeatureMapping("status_sus_path", "CONFIG_KSU_SUSFS_SUS_PATH"), + FeatureMapping("status_sus_mount", "CONFIG_KSU_SUSFS_SUS_MOUNT"), + FeatureMapping("status_auto_default_mount", "CONFIG_KSU_SUSFS_AUTO_ADD_SUS_KSU_DEFAULT_MOUNT"), + FeatureMapping("status_auto_bind_mount", "CONFIG_KSU_SUSFS_AUTO_ADD_SUS_BIND_MOUNT"), + FeatureMapping("status_sus_kstat", "CONFIG_KSU_SUSFS_SUS_KSTAT"), + FeatureMapping("status_try_umount", "CONFIG_KSU_SUSFS_TRY_UMOUNT"), + FeatureMapping("status_auto_try_umount_bind", "CONFIG_KSU_SUSFS_AUTO_ADD_TRY_UMOUNT_FOR_BIND_MOUNT"), + FeatureMapping("status_spoof_uname", "CONFIG_KSU_SUSFS_SPOOF_UNAME"), + FeatureMapping("status_enable_log", "CONFIG_KSU_SUSFS_ENABLE_LOG"), + FeatureMapping("status_hide_symbols", "CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS"), + FeatureMapping("status_spoof_cmdline", "CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG"), + FeatureMapping("status_open_redirect", "CONFIG_KSU_SUSFS_OPEN_REDIRECT"), + FeatureMapping("status_magic_mount", "CONFIG_KSU_SUSFS_HAS_MAGIC_MOUNT"), + FeatureMapping("status_overlayfs_auto_kstat", "CONFIG_KSU_SUSFS_SUS_OVERLAYFS") + ) + } + + /** + * 获取启用功能状态 + */ + suspend fun getEnabledFeatures(context: Context): List = withContext(Dispatchers.IO) { + try { + // 每次都重新执行命令获取最新状态 + val shell = getRootShell() + + // 首先检查二进制文件是否存在于目标位置 + val checkResult = shell.newJob().add("test -f '$SUSFS_TARGET_PATH'").exec() + + val binaryPath = if (checkResult.isSuccess) { + // 如果目标位置存在,直接使用 + SUSFS_TARGET_PATH + } else { + // 如果不存在,尝试从assets复制 + copyBinaryFromAssets(context) + } + + if (binaryPath == null) { + return@withContext emptyList() + } + + // 使用runCmd执行show enabled_features命令获取实时状态 + val command = "$binaryPath show enabled_features" + val output = runCmd(shell, command) + + if (output.isNotEmpty()) { + parseEnabledFeatures(context, output) + } else { + // 如果命令输出为空,返回空列表 + emptyList() + } + } catch (e: Exception) { + e.printStackTrace() + emptyList() + } + } + + /** + * 解析启用功能状态输出 + */ + private fun parseEnabledFeatures(context: Context, output: String): List { + val features = mutableListOf() + + // 将输出按行分割并保存到集合中进行快速查找 + val outputLines = output.lines().map { it.trim() }.filter { it.isNotEmpty() }.toSet() + + // 获取功能配置映射表 + val featureMappings = getFeatureMappings() + + // 定义功能名称映射(id到显示名称) + val featureNameMap = mapOf( + "status_sus_path" to context.getString(R.string.sus_path_feature_label), + "status_sus_mount" to context.getString(R.string.sus_mount_feature_label), + "status_try_umount" to context.getString(R.string.try_umount_feature_label), + "status_spoof_uname" to context.getString(R.string.spoof_uname_feature_label), + "status_spoof_cmdline" to context.getString(R.string.spoof_cmdline_feature_label), + "status_open_redirect" to context.getString(R.string.open_redirect_feature_label), + "status_enable_log" to context.getString(R.string.enable_log_feature_label), + "status_auto_default_mount" to context.getString(R.string.auto_default_mount_feature_label), + "status_auto_bind_mount" to context.getString(R.string.auto_bind_mount_feature_label), + "status_auto_try_umount_bind" to context.getString(R.string.auto_try_umount_bind_feature_label), + "status_hide_symbols" to context.getString(R.string.hide_symbols_feature_label), + "status_sus_kstat" to context.getString(R.string.sus_kstat_feature_label), + "status_magic_mount" to context.getString(R.string.magic_mount_feature_label), + "status_overlayfs_auto_kstat" to context.getString(R.string.overlayfs_auto_kstat_feature_label) + ) + + // 根据映射表检查每个功能的启用状态 + featureMappings.forEach { mapping -> + val displayName = featureNameMap[mapping.id] ?: mapping.id + val isEnabled = outputLines.contains(mapping.config) + features.add(EnabledFeature(displayName, isEnabled)) + } + + return features.sortedBy { it.name } + } + + /** + * 添加SUS路径 + */ + suspend fun addSusPath(context: Context, path: String): Boolean { + val success = executeSusfsCommand(context, "add_sus_path '$path'") + if (success) { + val currentPaths = getSusPaths(context).toMutableSet() + currentPaths.add(path) + saveSusPaths(context, currentPaths) + + // 如果开启了开机自启动,更新启动脚本 + if (isAutoStartEnabled(context)) { + createStartupScript(context) + } + } + return success + } + + /** + * 移除SUS路径 + */ + suspend fun removeSusPath(context: Context, path: String): Boolean { + val currentPaths = getSusPaths(context).toMutableSet() + currentPaths.remove(path) + saveSusPaths(context, currentPaths) + + // 如果开启了开机自启动,更新启动脚本 + if (isAutoStartEnabled(context)) { + createStartupScript(context) + } + + withContext(Dispatchers.Main) { + Toast.makeText(context, "已移除SUS路径: $path", Toast.LENGTH_SHORT).show() + } + return true + } + + /** + * 添加SUS挂载 + */ + suspend fun addSusMount(context: Context, mount: String): Boolean { + val success = executeSusfsCommand(context, "add_sus_mount '$mount'") + if (success) { + val currentMounts = getSusMounts(context).toMutableSet() + currentMounts.add(mount) + saveSusMounts(context, currentMounts) + + // 如果开启了开机自启动,更新启动脚本 + if (isAutoStartEnabled(context)) { + createStartupScript(context) + } + } + return success + } + + /** + * 移除SUS挂载 + */ + suspend fun removeSusMount(context: Context, mount: String): Boolean { + val currentMounts = getSusMounts(context).toMutableSet() + currentMounts.remove(mount) + saveSusMounts(context, currentMounts) + + // 如果开启了开机自启动,更新启动脚本 + if (isAutoStartEnabled(context)) { + createStartupScript(context) + } + + withContext(Dispatchers.Main) { + Toast.makeText(context, "已移除SUS挂载: $mount", Toast.LENGTH_SHORT).show() + } + return true + } + + /** + * 添加尝试卸载 + */ + suspend fun addTryUmount(context: Context, path: String, mode: Int): Boolean { + val success = executeSusfsCommand(context, "add_try_umount '$path' $mode") + if (success) { + val currentUmounts = getTryUmounts(context).toMutableSet() + currentUmounts.add("$path|$mode") + saveTryUmounts(context, currentUmounts) + + // 如果开启了开机自启动,更新启动脚本 + if (isAutoStartEnabled(context)) { + createStartupScript(context) + } + } + return success + } + + /** + * 移除尝试卸载 + */ + suspend fun removeTryUmount(context: Context, umountEntry: String): Boolean { + val currentUmounts = getTryUmounts(context).toMutableSet() + currentUmounts.remove(umountEntry) + saveTryUmounts(context, currentUmounts) + + // 如果开启了开机自启动,更新启动脚本 + if (isAutoStartEnabled(context)) { + createStartupScript(context) + } + + val parts = umountEntry.split("|") + val path = if (parts.isNotEmpty()) parts[0] else umountEntry + withContext(Dispatchers.Main) { + Toast.makeText(context, "已移除尝试卸载: $path", Toast.LENGTH_SHORT).show() + } + return true + } + + /** + * 运行尝试卸载 + */ + suspend fun runTryUmount(context: Context): Boolean { + return executeSusfsCommand(context, "run_try_umount") + } + + /** + * 设置Android Data路径 + */ + suspend fun setAndroidDataPath(context: Context, path: String): Boolean { + val success = executeSusfsCommand(context, "set_android_data_root_path '$path'") + if (success) { + saveAndroidDataPath(context, path) + + // 如果开启了开机自启动,更新启动脚本 + if (isAutoStartEnabled(context)) { + createStartupScript(context) + } + } + return success + } + + /** + * 设置SD卡路径 + */ + suspend fun setSdcardPath(context: Context, path: String): Boolean { + val success = executeSusfsCommand(context, "set_sdcard_root_path '$path'") + if (success) { + saveSdcardPath(context, path) + + // 如果开启了开机自启动,更新启动脚本 + if (isAutoStartEnabled(context)) { + createStartupScript(context) + } + } + return success + } + /** * 执行SuSFS命令设置uname */ @@ -241,7 +716,7 @@ fi // 如果开启了开机自启动,更新启动脚本 if (isAutoStartEnabled(context)) { - createStartupScript(unameValue) + createStartupScript(context) } withContext(Dispatchers.Main) { @@ -284,7 +759,8 @@ fi if (enabled) { // 启用开机自启动 val lastValue = getLastAppliedValue(context) - if (lastValue == DEFAULT_UNAME) { + if (lastValue == DEFAULT_UNAME && getSusPaths(context).isEmpty() && + getSusMounts(context).isEmpty() && getTryUmounts(context).isEmpty()) { withContext(Dispatchers.Main) { Toast.makeText( context, @@ -314,7 +790,7 @@ fi } } - val success = createStartupScript(lastValue) + val success = createStartupScript(context) if (success) { setAutoStartEnabled(context, true) withContext(Dispatchers.Main) { diff --git a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/MoreSettings.kt b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/MoreSettings.kt index 0ef4b406..a792ea0a 100644 --- a/manager/app/src/main/java/zako/zako/zako/zakoui/screen/MoreSettings.kt +++ b/manager/app/src/main/java/zako/zako/zako/zakoui/screen/MoreSettings.kt @@ -1090,107 +1090,106 @@ fun MoreSettingsScreen( onChange = onHideLinkCardChange ) } + KsuIsValid { + // 高级设置 + SettingsCard( + title = stringResource(R.string.advanced_settings) + ) { + // SELinux 开关 + SwitchSettingItem( + icon = Icons.Filled.Security, + title = stringResource(R.string.selinux), + summary = if (selinuxEnabled) + stringResource(R.string.selinux_enabled) else + stringResource(R.string.selinux_disabled), + checked = selinuxEnabled, + onChange = { enabled -> + val command = if (enabled) "setenforce 1" else "setenforce 0" + Shell.getShell().newJob().add(command).exec().let { result -> + if (result.isSuccess) { + selinuxEnabled = enabled + // 显示成功提示 + val message = if (enabled) + context.getString(R.string.selinux_enabled_toast) + else + context.getString(R.string.selinux_disabled_toast) - // 高级设置 - SettingsCard( - title = stringResource(R.string.advanced_settings) - ) { - // SELinux 开关 - KsuIsValid { - SwitchSettingItem( - icon = Icons.Filled.Security, - title = stringResource(R.string.selinux), - summary = if (selinuxEnabled) - stringResource(R.string.selinux_enabled) else - stringResource(R.string.selinux_disabled), - checked = selinuxEnabled, - onChange = { enabled -> - val command = if (enabled) "setenforce 1" else "setenforce 0" - Shell.getShell().newJob().add(command).exec().let { result -> - if (result.isSuccess) { - selinuxEnabled = enabled - // 显示成功提示 - val message = if (enabled) - context.getString(R.string.selinux_enabled_toast) - else - context.getString(R.string.selinux_disabled_toast) + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } else { + // 显示失败提示 + Toast.makeText( + context, + context.getString(R.string.selinux_change_failed), + Toast.LENGTH_SHORT + ).show() + } + } + } + ) - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } else { - // 显示失败提示 + // 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 = { showSuSFSConfigDialog = true } + ) + } + + // SuSFS 开关(仅在支持时显示) + val suSFS = getSuSFS() + val isSUS_SU = getSuSFSFeatures() + if (suSFS == "Supported" && isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") { + // 默认启用 + var isEnabled by rememberSaveable { + mutableStateOf(true) + } + + // 在启动时检查状态 + LaunchedEffect(Unit) { + // 如果当前模式不是2就强制启用 + val currentMode = susfsSUS_SU_Mode() + val wasManuallyDisabled = prefs.getBoolean("enable_sus_su", true) + if (currentMode != "2" && wasManuallyDisabled) { + susfsSUS_SU_2() // 强制切换到模式2 + prefs.edit { putBoolean("enable_sus_su", true) } + } + isEnabled = currentMode == "2" + } + + SwitchSettingItem( + icon = Icons.Filled.Security, + title = stringResource(id = R.string.settings_susfs_toggle), + summary = stringResource(id = R.string.settings_susfs_toggle_summary), + checked = isEnabled, + onChange = { + if (it) { + // 手动启用 + susfsSUS_SU_2() + prefs.edit { putBoolean("enable_sus_su", true) } Toast.makeText( context, - context.getString(R.string.selinux_change_failed), + context.getString(R.string.susfs_enabled), + Toast.LENGTH_SHORT + ).show() + } else { + // 手动关闭 + susfsSUS_SU_0() + prefs.edit { putBoolean("enable_sus_su", false) } + Toast.makeText( + context, + context.getString(R.string.susfs_disabled), Toast.LENGTH_SHORT ).show() } + isEnabled = it } - } - ) - } - - // 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 = { showSuSFSConfigDialog = true } - ) - } - - // SuSFS 开关(仅在支持时显示) - val suSFS = getSuSFS() - val isSUS_SU = getSuSFSFeatures() - if (suSFS == "Supported" && isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") { - // 默认启用 - var isEnabled by rememberSaveable { - mutableStateOf(true) + ) } - - // 在启动时检查状态 - LaunchedEffect(Unit) { - // 如果当前模式不是2就强制启用 - val currentMode = susfsSUS_SU_Mode() - val wasManuallyDisabled = prefs.getBoolean("enable_sus_su", true) - if (currentMode != "2" && wasManuallyDisabled) { - susfsSUS_SU_2() // 强制切换到模式2 - prefs.edit { putBoolean("enable_sus_su", true) } - } - isEnabled = currentMode == "2" - } - - SwitchSettingItem( - icon = Icons.Filled.Security, - title = stringResource(id = R.string.settings_susfs_toggle), - summary = stringResource(id = R.string.settings_susfs_toggle_summary), - checked = isEnabled, - onChange = { - if (it) { - // 手动启用 - susfsSUS_SU_2() - prefs.edit { putBoolean("enable_sus_su", true) } - Toast.makeText( - context, - context.getString(R.string.susfs_enabled), - Toast.LENGTH_SHORT - ).show() - } else { - // 手动关闭 - susfsSUS_SU_0() - prefs.edit { putBoolean("enable_sus_su", false) } - Toast.makeText( - context, - context.getString(R.string.susfs_disabled), - Toast.LENGTH_SHORT - ).show() - } - isEnabled = it - } - ) } } } diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index 07fff863..d5e0f262 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -72,7 +72,7 @@ 为 %s 更新 App Profile 失败 当前 KernelSU 版本 %d 过低,管理器无法正常工作,请将内核 KernelSU 版本升级至 %d 或以上! 默认卸载模块 - App Profile 中“卸载模块”的全局默认值,如果启用,将会为没有设置 Profile 的应用移除所有模块针对系统的修改。 + App Profile 中"卸载模块"的全局默认值,如果启用,将会为没有设置 Profile 的应用移除所有模块针对系统的修改。 禁用 kprobe 钩子 启用该选项后将允许 KernelSU 为本应用还原被模块修改过的文件。 @@ -129,7 +129,7 @@ 恢复原厂镜像 临时卸载 KernelSU,下次重启后恢复至原始状态。 完全并永久卸载 KernelSU(Root 权限和所有模块)。 - 恢复原厂镜像(若存在备份),一般在 OTA 前使用;如果你需要卸载 KernelSU,请使用“永久卸载”。 + 恢复原厂镜像(若存在备份),一般在 OTA 前使用;如果你需要卸载 KernelSU,请使用"永久卸载"。 刷写中 刷写完成 刷写失败 @@ -405,9 +405,9 @@ 当前值: %s 重置为默认值 应用 + 完成 确认重置 - 确定要将 SuSFS uname 重置为默认值吗?此操作不可撤销。 确认重置 无法找到 ksu_susfs 文件 @@ -420,6 +420,7 @@ 开机自启动 系统启动时自动应用 uname 配置 + 需要配置uname或添加路径后才能启用 开机自启动已启用 开机自启动已禁用 启用开机自启动失败 @@ -427,4 +428,80 @@ 开机自启动配置错误: %s 没有可用的配置进行开机自启动 需要输入非默认值才能启用开机自启动 - + + 基本设置 + SUS路径 + SUS挂载 + 尝试卸载 + 路径设置 + 启用功能状态 + + 添加 + 删除 + 重置 + 运行 + 刷新 + + 添加SUS路径 + 添加SUS挂载 + 添加尝试卸载 + 路径 + 挂载路径 + 例如: /system/addon.d + SUS路径管理 + SUS挂载管理 + 尝试卸载管理 + 暂无SUS路径配置 + 暂无SUS挂载配置 + 暂无尝试卸载配置 + + 卸载模式 + 普通卸载 (0) + 分离卸载 (1) + 普通 + 分离 + 模式: %1$s (%2$s) + + 确认运行尝试卸载 + 这将立即执行所有已配置的尝试卸载操作,确定要继续吗? + + 重置SUS路径 + 这将清除所有SUS路径配置,确定要继续吗? + 重置SUS挂载 + 这将清除所有SUS挂载配置,确定要继续吗? + 重置尝试卸载 + 这将清除所有尝试卸载配置,确定要继续吗? + + 路径设置 + Android Data路径 + SD卡路径 + 设置Android Data路径 + 设置SD卡路径 + + 启用功能状态 + 显示当前SuSFS启用的功能状态 + 未找到功能状态信息 + 已启用 + 已禁用 + + SUS 路径支持 + SUS 挂载支持 + 尝试卸载支持 + 欺骗 uname 支持 + 欺骗 Cmdline/Bootconfig + 开放重定向支持 + 日志记录支持 + 自动默认挂载 + 自动绑定挂载 + 自动尝试卸载绑定挂载 + 隐藏 KSU SUSFS 符号 + 魔法坐骑支持 + OverlayFS 自动内核统计支持 + SUS Kstat Support + + 添加新项目 + 运行卸载操作 + 删除项目 + 重置此部分 + 刷新功能状态 + \ No newline at end of file diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 8dd1a4cc..29229447 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -407,9 +407,9 @@ Current value: %s Reset to Default Apply + Done Confirm Reset - Are you sure you want to reset SuSFS uname to default value? This action cannot be undone. Confirm Reset Cannot find ksu_susfs file @@ -420,13 +420,90 @@ SuSFS Configuration Configure SuSFS uname value (Current: %s) - boot-up - Automatic application of uname configuration at system startup - Boot Self-Start is enabled - Boot Self-Start is disabled - Failed to enable boot self-start - Failure to disable boot-up - Boot-up misconfiguration: %s - There is no available configuration for boot self-start - Need to enter a non-default value to enable bootstrapping - + Auto Start + Automatically apply uname configuration at system startup + Need to configure uname or add paths to enable + Auto Start enabled + Auto Start disabled + Failed to enable auto start + Failed to disable auto start + Auto start configuration error: %s + No available configuration for auto start + Need to enter a non-default value to enable auto start + + Basic Settings + SUS Paths + SUS Mounts + Try Umount + Path Settings + Enabled Features Status + + Add + Delete + Reset + Run + Refresh + + Add SUS Path + Add SUS Mount + Add Try Umount + Path + Mount Path + e.g.: /system/addon.d + SUS Paths Management + SUS Mounts Management + Try Umount Management + No SUS paths configured + No SUS mounts configured + No try umount configured + + Umount Mode + Normal Umount (0) + Detach Umount (1) + Normal + Detach + Mode: %1$s (%2$s) + + Confirm Run Try Umount + This will immediately execute all configured try umount operations. Are you sure you want to continue? + + Reset SUS Paths + This will clear all SUS path configurations. Are you sure you want to continue? + Reset SUS Mounts + This will clear all SUS mount configurations. Are you sure you want to continue? + Reset Try Umount + This will clear all try umount configurations. Are you sure you want to continue? + + Path Settings + Android Data Path + SD Card Path + Set Android Data Path + Set SD Card Path + + Enabled Features Status + Display current SuSFS enabled features status + No feature status information found + Enabled + Disabled + + SUS Path Support + SUS Mount Support + Try Umount Support + Spoof uname Support + Spoof Cmdline/Bootconfig + Open Redirect Support + Logging Support + Auto Default Mount + Auto Bind Mount + Auto Try Umount Bind Mount + Hide KSU SUSFS Symbols + Magic Mount Support + OverlayFS Auto Kernel Stat Support + SUS Kstat Support + + Add new item + Run umount operations + Delete item + Reset this section + Refresh feature status + \ No newline at end of file