You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
manager: hide reboot button if ksuversion not found (KSU_NEXT v2 signature not found)
manager: add module migration, backup and restore manager: update translation for zh-CN (authored by @xiangxiangxiong9) docs: devies: add more unofficial supported devices
This commit is contained in:
@@ -52,10 +52,14 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
val kernelVersion = getKernelVersion()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
kernelVersion,
|
||||
ksuVersion,
|
||||
onInstallClick = {
|
||||
navigator.navigate(InstallScreenDestination)
|
||||
},
|
||||
@@ -72,8 +76,6 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
val lkmMode = ksuVersion?.let {
|
||||
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null
|
||||
}
|
||||
@@ -164,6 +166,7 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
kernelVersion: KernelVersion,
|
||||
ksuVersion: Int?,
|
||||
onInstallClick: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
@@ -179,30 +182,31 @@ private fun TopBar(
|
||||
}
|
||||
}
|
||||
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = {
|
||||
showDropdown = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
contentDescription = stringResource(id = R.string.reboot)
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||
showDropdown = false
|
||||
if (ksuVersion != null) {
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = {
|
||||
showDropdown = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Refresh,
|
||||
contentDescription = stringResource(id = R.string.reboot)
|
||||
)
|
||||
|
||||
RebootDropdownItem(id = R.string.reboot)
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||
showDropdown = false
|
||||
}) {
|
||||
RebootDropdownItem(id = R.string.reboot)
|
||||
|
||||
val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||
val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||
}
|
||||
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||
}
|
||||
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -354,6 +358,9 @@ private fun InfoCard() {
|
||||
mutableStateOf(prefs.getBoolean("use_overlay_fs", false))
|
||||
}
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
useOverlayFs = prefs.getBoolean("use_overlay_fs", false)
|
||||
}
|
||||
@@ -432,10 +439,10 @@ private fun InfoCard() {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(
|
||||
label = stringResource(R.string.home_module_mount),
|
||||
content = if (useOverlayFs) {
|
||||
stringResource(R.string.home_overlayfs_mount)
|
||||
} else {
|
||||
stringResource(R.string.home_magic_mount)
|
||||
content = when {
|
||||
ksuVersion == null -> stringResource(R.string.unavailable)
|
||||
useOverlayFs -> stringResource(R.string.home_overlayfs_mount)
|
||||
else -> stringResource(R.string.home_magic_mount)
|
||||
},
|
||||
icon = Icons.Filled.SettingsSuggest,
|
||||
)
|
||||
|
||||
@@ -96,6 +96,9 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
@@ -110,6 +113,8 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
val loadingDialog = rememberLoadingDialog()
|
||||
val shrinkDialog = rememberConfirmDialog()
|
||||
val restoreDialog = rememberConfirmDialog()
|
||||
val backupDialog = rememberConfirmDialog()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -138,26 +143,30 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
|
||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
||||
headlineContent = { Text(profileTemplate) },
|
||||
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||
}
|
||||
)
|
||||
if (ksuVersion != null) {
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
||||
headlineContent = { Text(profileTemplate) },
|
||||
supportingContent = { Text(stringResource(id = R.string.settings_profile_template_summary)) },
|
||||
modifier = Modifier.clickable {
|
||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var umountChecked by rememberSaveable {
|
||||
mutableStateOf(Natives.isDefaultUmountModules())
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.RemoveModerator,
|
||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||
checked = umountChecked
|
||||
) {
|
||||
if (Natives.setDefaultUmountModules(it)) {
|
||||
umountChecked = it
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.RemoveModerator,
|
||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||
checked = umountChecked
|
||||
) {
|
||||
if (Natives.setDefaultUmountModules(it)) {
|
||||
umountChecked = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,14 +186,16 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
var showWarningDialog by remember { mutableStateOf(false) }
|
||||
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Build,
|
||||
title = stringResource(id = R.string.use_overlay_fs),
|
||||
summary = stringResource(id = R.string.use_overlay_fs_summary),
|
||||
checked = useOverlayFs
|
||||
) {
|
||||
if (!hasShownWarning.value) {
|
||||
showWarningDialog = true
|
||||
if (ksuVersion != null) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.Build,
|
||||
title = stringResource(id = R.string.use_overlay_fs),
|
||||
summary = stringResource(id = R.string.use_overlay_fs_summary),
|
||||
checked = useOverlayFs
|
||||
) {
|
||||
if (!hasShownWarning.value) {
|
||||
showWarningDialog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +209,11 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
showWarningDialog = false
|
||||
prefs.edit().putBoolean("use_overlay_fs", !useOverlayFs).apply()
|
||||
useOverlayFs = !useOverlayFs
|
||||
if (useOverlayFs) {
|
||||
moduleBackup()
|
||||
} else {
|
||||
moduleMigration()
|
||||
}
|
||||
if (isManager) install()
|
||||
showRebootDialog = true
|
||||
}) {
|
||||
@@ -373,6 +389,54 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
}
|
||||
|
||||
if (ksuVersion != null) {
|
||||
val moduleBackup = stringResource(id = R.string.module_backup)
|
||||
val backupMessage = stringResource(id = R.string.module_backup_message)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Backup,
|
||||
moduleBackup
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(moduleBackup) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val result = backupDialog.awaitConfirm(title = moduleBackup, content = backupMessage)
|
||||
if (result == ConfirmResult.Confirmed) {
|
||||
loadingDialog.withLoading {
|
||||
moduleBackup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (ksuVersion != null) {
|
||||
val moduleRestore = stringResource(id = R.string.module_restore)
|
||||
val restoreMessage = stringResource(id = R.string.module_restore_message)
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Filled.Restore,
|
||||
moduleRestore
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(moduleRestore) },
|
||||
modifier = Modifier.clickable {
|
||||
scope.launch {
|
||||
val result = restoreDialog.awaitConfirm(title = moduleRestore, content = restoreMessage)
|
||||
if (result == ConfirmResult.Confirmed) {
|
||||
loadingDialog.withLoading {
|
||||
moduleRestore()
|
||||
showRebootDialog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (useOverlayFs) {
|
||||
val shrink = stringResource(id = R.string.shrink_sparse_image)
|
||||
|
||||
@@ -442,6 +442,67 @@ fun getFileName(context: Context, uri: Uri): String {
|
||||
return name
|
||||
}
|
||||
|
||||
fun moduleBackupDir(): String? {
|
||||
val shell = getRootShell()
|
||||
val baseBackupDir = "/data/adb/modules_bak"
|
||||
val resultBase = ShellUtils.fastCmd(shell, "mkdir -p $baseBackupDir").trim()
|
||||
if (resultBase.isNotEmpty()) return null
|
||||
|
||||
val timestamp = ShellUtils.fastCmd(shell, "date +%Y%m%d_%H%M%S").trim()
|
||||
if (timestamp.isEmpty()) return null
|
||||
|
||||
val newBackupDir = "$baseBackupDir/$timestamp"
|
||||
val resultNewDir = ShellUtils.fastCmd(shell, "mkdir -p $newBackupDir").trim()
|
||||
|
||||
if (resultNewDir.isEmpty()) return newBackupDir
|
||||
return null
|
||||
}
|
||||
|
||||
fun moduleBackup(): Boolean {
|
||||
val shell = getRootShell()
|
||||
|
||||
val checkEmptyCommand = "if [ -z \"$(ls -A /data/adb/modules)\" ]; then echo 'empty'; fi"
|
||||
val resultCheckEmpty = ShellUtils.fastCmd(shell, checkEmptyCommand).trim()
|
||||
|
||||
if (resultCheckEmpty == "empty") {
|
||||
return false
|
||||
}
|
||||
|
||||
val backupDir = moduleBackupDir() ?: return false
|
||||
val command = "cp -rp /data/adb/modules/* $backupDir"
|
||||
val result = ShellUtils.fastCmd(shell, command).trim()
|
||||
|
||||
return result.isEmpty()
|
||||
}
|
||||
|
||||
fun moduleMigration(): Boolean {
|
||||
val shell = getRootShell()
|
||||
val command = "cp -rp /data/adb/modules/* /data/adb/modules_update"
|
||||
val result = ShellUtils.fastCmd(shell, command).trim()
|
||||
|
||||
return result.isEmpty()
|
||||
}
|
||||
|
||||
fun moduleRestore(): Boolean {
|
||||
val shell = getRootShell()
|
||||
|
||||
val command = "ls -t /data/adb/modules_bak | head -n 1"
|
||||
val latestBackupDir = ShellUtils.fastCmd(shell, command).trim()
|
||||
|
||||
if (latestBackupDir.isEmpty()) return false
|
||||
|
||||
val sourceDir = "/data/adb/modules_bak/$latestBackupDir"
|
||||
val destinationDir = "/data/adb/modules_update"
|
||||
|
||||
val createDestDirCommand = "mkdir -p $destinationDir"
|
||||
ShellUtils.fastCmd(shell, createDestDirCommand)
|
||||
|
||||
val moveCommand = "cp -rp $sourceDir/* $destinationDir"
|
||||
val result = ShellUtils.fastCmd(shell, moveCommand).trim()
|
||||
|
||||
return result.isEmpty()
|
||||
}
|
||||
|
||||
fun setAppProfileTemplate(id: String, template: String): Boolean {
|
||||
val shell = getRootShell()
|
||||
val escapedTemplate = template.replace("\"", "\\\"")
|
||||
|
||||
@@ -69,6 +69,10 @@
|
||||
<string name="use_overlay_fs_summary">토글 하여 KernelSU Next의 모듈 마운트 시스템을 Magic Mount와 OverlayFS 간에 전환합니다.</string>
|
||||
<string name="reboot_required">다시 시작 필요</string>
|
||||
<string name="reboot_message">시스템을 다시 시작한 후에 변경 사항들이 적용됩니다. 지금 다시 시작하시겠습니까?</string>
|
||||
<string name="warning">경고</string>
|
||||
<string name="warning_message">이 기능은 아직 베타 단계이며 개발 중입니다. 계속하기 전에 모듈들을 백업해 놓으시기를 바랍니다. 위험를 이해한 경우에만 이 기능을 이용하세요. 주의하여 계속하세요.</string>
|
||||
<string name="proceed">계속</string>
|
||||
<string name="cancel">취소</string>
|
||||
<string name="later">나중에</string>
|
||||
<string name="home_next_kernelsu">🔥 Next 빌드</string>
|
||||
<string name="home_next_kernelsu_repo">https://github.com/rifsxd/KernelSU-Next</string>
|
||||
@@ -78,7 +82,7 @@
|
||||
<string name="home_experimental_kernelsu_body">KernelSU Next는 활발한 실험적 개발 단계에 있는 비공식 버전입니다. 안정성, 성능 또는 신뢰성에 대한 보장 없이 있는 그대로 제공됩니다.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_1"> • 본인 책임하에 사용: 충돌, 예기치 않은 동작 또는 시스템 문제가 발생할 수 있습니다.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_2"> • 워런티 없음: 개발자들은 데이터 손실, 시스템 손상, 사용으로 인한 그 외 오류들에 대해 책임지지 않습니다.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_3"> • 테스트 목적으로만 사용: 리스크를 이해하고 문제 해결에 능숙한 사용자들을 위한 것입니다.</string>
|
||||
<string name="home_experimental_kernelsu_body_point_3"> • 테스트 목적으로만 사용: 위험를 이해하고 문제 해결에 능숙한 사용자들을 위한 것입니다.</string>
|
||||
<string name="about_source_code"><![CDATA[%1$s에서 소스 코드 보기]]></string>
|
||||
<string name="profile" translatable="false">앱 프로파일</string>
|
||||
<string name="profile_default">기본</string>
|
||||
@@ -161,4 +165,4 @@
|
||||
<string name="selected_lkm">선택된 LKM: %s</string>
|
||||
<string name="save_log">로그 저장</string>
|
||||
<string name="log_saved">로그 저장됨</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
<string name="module_empty">没有安装模块</string>
|
||||
<string name="module">模块</string>
|
||||
<string name="module_install_prompt_with_name">是否要继续安装模块 %1$s ?</string>
|
||||
<string name="module_sort_a_to_z">种类 (A - Z)</string>
|
||||
<string name="module_sort_z_to_a">种类 (Z - A)</string>
|
||||
<string name="module_sort_a_to_z">按 A - Z 排序</string>
|
||||
<string name="module_sort_z_to_a">按 Z - A 排序</string>
|
||||
<string name="uninstall">卸载</string>
|
||||
<string name="restore">重启</string>
|
||||
<string name="restore">恢复</string>
|
||||
<string name="module_install">安装</string>
|
||||
<string name="install">安装</string>
|
||||
<string name="reboot">重启</string>
|
||||
@@ -61,16 +61,30 @@
|
||||
<string name="send_log">发送日志</string>
|
||||
<string name="safe_mode">安全模式</string>
|
||||
<string name="reboot_to_apply">重启生效</string>
|
||||
<string name="module_magisk_conflict">由于与 Magisk 有冲突,所有模块不可用!</string>
|
||||
<string name="home_module_mount">模块系统</string>
|
||||
<string name="home_magic_mount">Magic Mount</string>
|
||||
<string name="home_overlayfs_mount">OverlayFS</string>
|
||||
<string name="module_magisk_conflict">因与 Magisk 有冲突,所有模块不可用!</string>
|
||||
<string name="unavailable">不可用</string>
|
||||
<string name="use_overlay_fs">使用 OverlayFS (Beta)</string>
|
||||
<string name="use_overlay_fs_summary">对于 KernelSU Next 的挂载系统,在使用 OverlayFS 和 Magic Mount 之间进行切换。</string>
|
||||
<string name="reboot_required">需要重启</string>
|
||||
<string name="reboot_message">更改将在重启系统后生效。您想现在重启吗?</string>
|
||||
<string name="module_restore">恢复模块</string>
|
||||
<string name="module_restore_message">从备份中恢复模块。</string>
|
||||
<string name="module_backup">备份模块</string>
|
||||
<string name="module_backup_message">备份当前已安装的模块。</string>
|
||||
<string name="warning">警告</string>
|
||||
<string name="warning_message">此功能仍处于测试阶段且在开发中。请确保在继续之前备份您的模块。仅当您了解潜在风险时才能使用此功能。谨慎行事。</string>
|
||||
<string name="proceed">继续</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="later">稍后</string>
|
||||
<string name="home_next_kernelsu">🔥 Next 构建</string>
|
||||
<string name="home_next_kernelsu_repo">https://github.com/rifsxd/KernelSU-Next</string>
|
||||
<string name="home_next_kernelsu_body">Next 实验性分支。在 GitHub 上查看!</string>
|
||||
<string name="home_experimental_kernelsu">⚠️ 实验性开发警告!</string>
|
||||
<string name="home_experimental_kernelsu_repo">127.0.0.1</string>
|
||||
<string name="home_experimental_kernelsu_body">Next 是非官方版本,始终处于积极的实验开发阶段。它按原样提供,不保证稳定性、性能或可靠性。</string>
|
||||
<string name="home_experimental_kernelsu_body">KernelSU Next 是非官方版本,始终处于积极的实验开发阶段。它按原样提供,不保证稳定性、性能或可靠性。</string>
|
||||
<string name="home_experimental_kernelsu_body_point_1"> • 使用风险自负:可能会发生崩溃、意外行为或导致系统问题。</string>
|
||||
<string name="home_experimental_kernelsu_body_point_2"> • 无担保:开发人员不对因使用而产生的任何数据丢失、系统损坏或其他后果负责。</string>
|
||||
<string name="home_experimental_kernelsu_body_point_3"> • 仅用于测试目的:它适用于了解风险并能轻松解决问题的用户。</string>
|
||||
|
||||
@@ -65,10 +65,15 @@
|
||||
<string name="home_module_mount">Module system</string>
|
||||
<string name="home_magic_mount">Magic Mount</string>
|
||||
<string name="home_overlayfs_mount">OverlayFS</string>
|
||||
<string name="unavailable">Unavailable</string>
|
||||
<string name="use_overlay_fs">Use OverlayFS (Beta)</string>
|
||||
<string name="use_overlay_fs_summary">Toggle between using OverlayFS over Magic Mount for KernelSU Next\'s mount system.</string>
|
||||
<string name="reboot_required">Reboot Required</string>
|
||||
<string name="reboot_message">Changes will take effect after rebooting the system. Would you like to reboot now?</string>
|
||||
<string name="module_restore">Restore module</string>
|
||||
<string name="module_restore_message">Restore modules from recent backup.</string>
|
||||
<string name="module_backup">Backup module</string>
|
||||
<string name="module_backup_message">Backup currently installed modules.</string>
|
||||
<string name="warning">Warning</string>
|
||||
<string name="warning_message">This feature is still in beta and under development. Please ensure you backup your modules before proceeding. Use this feature only if you understand the potential risks. Proceed with caution.</string>
|
||||
<string name="proceed">Proceed</string>
|
||||
|
||||
Reference in New Issue
Block a user