diff --git a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/kpm.kt b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/kpm.kt index 5165332a..feb187c2 100644 --- a/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/kpm.kt +++ b/manager/app/src/main/java/shirkneko/zako/sukisu/ui/screen/kpm.kt @@ -29,7 +29,6 @@ import shirkneko.zako.sukisu.R import shirkneko.zako.sukisu.ui.component.ConfirmResult import shirkneko.zako.sukisu.ui.component.SearchAppBar import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog -import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog import shirkneko.zako.sukisu.ui.theme.getCardColors import shirkneko.zako.sukisu.ui.theme.getCardElevation import shirkneko.zako.sukisu.ui.viewmodel.KpmViewModel @@ -38,6 +37,8 @@ import shirkneko.zako.sukisu.ui.util.unloadKpmModule import java.io.File import androidx.core.content.edit import shirkneko.zako.sukisu.ui.theme.ThemeConfig +import shirkneko.zako.sukisu.ui.component.rememberCustomDialog +import shirkneko.zako.sukisu.ui.component.ConfirmDialogHandle /** * KPM 管理界面 @@ -55,7 +56,6 @@ fun KpmScreen( val scope = rememberCoroutineScope() val snackBarHost = remember { SnackbarHostState() } val confirmDialog = rememberConfirmDialog() - val loadingDialog = rememberLoadingDialog() val cardColor = if (!ThemeConfig.useDynamicColor) { ThemeConfig.currentTheme.ButtonContrast } else { @@ -64,17 +64,88 @@ fun KpmScreen( val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - val kpmInstall = stringResource(R.string.kpm_install) - val kpmInstallConfirm = stringResource(R.string.kpm_install_confirm) val kpmInstallSuccess = stringResource(R.string.kpm_install_success) val kpmInstallFailed = stringResource(R.string.kpm_install_failed) - val install = stringResource(R.string.install) val cancel = stringResource(R.string.cancel) - val kpmUninstall = stringResource(R.string.kpm_uninstall) - val kpmUninstallConfirmTemplate = stringResource(R.string.kpm_uninstall_confirm) val uninstall = stringResource(R.string.uninstall) + val FailedtoCheckModuleFile = stringResource(R.string.snackbar_failed_to_check_module_file) val kpmUninstallSuccess = stringResource(R.string.kpm_uninstall_success) val kpmUninstallFailed = stringResource(R.string.kpm_uninstall_failed) + val kpmInstallMode = stringResource(R.string.kpm_install_mode) + val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load) + val kpmInstallModeEmbed = stringResource(R.string.kpm_install_mode_embed) + val kpmInstallModeDescription = stringResource(R.string.kpm_install_mode_description) + + var tempFileForInstall by remember { mutableStateOf(null) } + val installModeDialog = rememberCustomDialog { dismiss -> + AlertDialog( + onDismissRequest = { + dismiss() + tempFileForInstall?.delete() + tempFileForInstall = null + }, + title = { Text(kpmInstallMode) }, + text = { Text(kpmInstallModeDescription) }, + confirmButton = { + Column { + Button( + onClick = { + scope.launch { + dismiss() + tempFileForInstall?.let { tempFile -> + handleModuleInstall( + tempFile = tempFile, + isEmbed = false, + viewModel = viewModel, + snackBarHost = snackBarHost, + kpmInstallSuccess = kpmInstallSuccess, + kpmInstallFailed = kpmInstallFailed + ) + } + tempFileForInstall = null + } + } + ) { + Text(kpmInstallModeLoad) + } + Spacer(modifier = Modifier.height(8.dp)) + Button( + onClick = { + scope.launch { + dismiss() + tempFileForInstall?.let { tempFile -> + handleModuleInstall( + tempFile = tempFile, + isEmbed = true, + viewModel = viewModel, + snackBarHost = snackBarHost, + kpmInstallSuccess = kpmInstallSuccess, + kpmInstallFailed = kpmInstallFailed + ) + } + tempFileForInstall = null + } + } + ) { + Text(kpmInstallModeEmbed) + } + } + }, + dismissButton = { + TextButton( + onClick = { + dismiss() + tempFileForInstall?.delete() + tempFileForInstall = null + } + ) { + Text(cancel) + } + } + ) + } + + val selectPatchLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() @@ -84,8 +155,9 @@ fun KpmScreen( val uri = result.data?.data ?: return@rememberLauncherForActivityResult scope.launch { - // 复制文件到临时目录 - val tempFile = File(context.cacheDir, "temp_patch.kpm") + val fileName = uri.lastPathSegment ?: "unknown.kpm" + val tempFile = File(context.cacheDir, fileName) + context.contentResolver.openInputStream(uri)?.use { input -> tempFile.outputStream().use { output -> input.copyTo(output) @@ -101,43 +173,8 @@ fun KpmScreen( return@launch } - val confirmResult = confirmDialog.awaitConfirm( - title = kpmInstall, - content = kpmInstallConfirm, - confirm = install, - dismiss = cancel - ) - - if (confirmResult == ConfirmResult.Confirmed) { - val success = loadingDialog.withLoading { - try { - val loadResult = loadKpmModule(tempFile.absolutePath) - if (true && loadResult.startsWith("Error")) { - Log.e("KsuCli", "Failed to load KPM module: $loadResult") - false - } else { - true - } - } catch (e: Exception) { - Log.e("KsuCli", "Failed to load KPM module: ${e.message}", e) - false - } - } - - if (success) { - viewModel.fetchModuleList() - snackBarHost.showSnackbar( - message = kpmInstallSuccess, - duration = SnackbarDuration.Short - ) - } else { - snackBarHost.showSnackbar( - message = kpmInstallFailed, - duration = SnackbarDuration.Short - ) - } - } - tempFile.delete() + tempFileForInstall = tempFile + installModeDialog.show() } } @@ -234,46 +271,21 @@ fun KpmScreen( verticalArrangement = Arrangement.spacedBy(16.dp) ) { items(viewModel.moduleList) { module -> - val kpmUninstallConfirm = String.format(kpmUninstallConfirmTemplate, module.name) KpmModuleItem( module = module, onUninstall = { scope.launch { - val confirmResult = confirmDialog.awaitConfirm( - title = kpmUninstall, - content = kpmUninstallConfirm, - confirm = uninstall, - dismiss = cancel + handleModuleUninstall( + module = module, + viewModel = viewModel, + snackBarHost = snackBarHost, + kpmUninstallSuccess = kpmUninstallSuccess, + kpmUninstallFailed = kpmUninstallFailed, + confirmDialog = confirmDialog, + FailedtoCheckModuleFile = FailedtoCheckModuleFile, + uninstall = uninstall, + cancel = cancel ) - if (confirmResult == ConfirmResult.Confirmed) { - val success = loadingDialog.withLoading { - try { - val unloadResult = unloadKpmModule(module.id) - if (true && unloadResult.startsWith("Error")) { - Log.e("KsuCli", "Failed to unload KPM module: $unloadResult") - false - } else { - true - } - } catch (e: Exception) { - Log.e("KsuCli", "Failed to unload KPM module: ${e.message}", e) - false - } - } - - if (success) { - viewModel.fetchModuleList() - snackBarHost.showSnackbar( - message = kpmUninstallSuccess, - duration = SnackbarDuration.Short - ) - } else { - snackBarHost.showSnackbar( - message = kpmUninstallFailed, - duration = SnackbarDuration.Short - ) - } - } } }, onControl = { @@ -287,6 +299,111 @@ fun KpmScreen( } } +private suspend fun handleModuleInstall( + tempFile: File, + isEmbed: Boolean, + viewModel: KpmViewModel, + snackBarHost: SnackbarHostState, + kpmInstallSuccess: String, + kpmInstallFailed: String +) { + val moduleName = tempFile.nameWithoutExtension + + try { + if (isEmbed) { + val targetPath = "/data/adb/kpm/$moduleName.kpm" + Runtime.getRuntime().exec(arrayOf("su", "-c", "mkdir -p /data/adb/kpm")).waitFor() + Runtime.getRuntime().exec(arrayOf("su", "-c", "cp ${tempFile.absolutePath} $targetPath")).waitFor() + } + + val loadResult = loadKpmModule(tempFile.absolutePath) + if (loadResult.startsWith("Error")) { + Log.e("KsuCli", "Failed to load KPM module: $loadResult") + snackBarHost.showSnackbar( + message = kpmInstallFailed, + duration = SnackbarDuration.Short + ) + } else { + viewModel.fetchModuleList() + snackBarHost.showSnackbar( + message = kpmInstallSuccess, + duration = SnackbarDuration.Short + ) + } + } catch (e: Exception) { + Log.e("KsuCli", "Failed to load KPM module: ${e.message}", e) + snackBarHost.showSnackbar( + message = kpmInstallFailed, + duration = SnackbarDuration.Short + ) + } + tempFile.delete() +} + +private suspend fun handleModuleUninstall( + module: KpmViewModel.ModuleInfo, + viewModel: KpmViewModel, + snackBarHost: SnackbarHostState, + kpmUninstallSuccess: String, + kpmUninstallFailed: String, + FailedtoCheckModuleFile: String, + uninstall: String, + cancel: String, + confirmDialog: ConfirmDialogHandle +) { + val moduleFileName = "primary:${module.id}.kpm" + val moduleFilePath = "/data/adb/kpm/$moduleFileName" + + val fileExists = try { + val result = Runtime.getRuntime().exec(arrayOf("su", "-c", "ls /data/adb/kpm/$moduleFileName")).waitFor() == 0 + result + } catch (e: Exception) { + Log.e("KsuCli", "Failed to check module file existence: ${e.message}", e) + snackBarHost.showSnackbar( + message = FailedtoCheckModuleFile, + duration = SnackbarDuration.Short + ) + false + } + + val confirmResult = confirmDialog.awaitConfirm( + title = "将卸载以下kpm模块:\n$moduleFileName", + content = "The following kpm modules will be uninstalled:\n$moduleFileName", + confirm = uninstall, + dismiss = cancel + ) + + if (confirmResult == ConfirmResult.Confirmed) { + try { + val unloadResult = unloadKpmModule(module.id) + if (unloadResult.startsWith("Error")) { + Log.e("KsuCli", "Failed to unload KPM module: $unloadResult") + snackBarHost.showSnackbar( + message = kpmUninstallFailed, + duration = SnackbarDuration.Short + ) + return + } + + if (fileExists) { + Runtime.getRuntime().exec(arrayOf("su", "-c", "rm $moduleFilePath")).waitFor() + } + + viewModel.fetchModuleList() + snackBarHost.showSnackbar( + message = kpmUninstallSuccess, + duration = SnackbarDuration.Short + ) + } catch (e: Exception) { + Log.e("KsuCli", "Failed to unload KPM module: ${e.message}", e) + snackBarHost.showSnackbar( + message = kpmUninstallFailed, + duration = SnackbarDuration.Short + ) + } + } +} + @Composable private fun KpmModuleItem( module: KpmViewModel.ModuleInfo, 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 5551e9df..246e8314 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -230,11 +230,9 @@ 版本 作者 卸载 - 确定要卸载内核模块 %1$s 吗? 卸载成功 卸载失败 - 加载 kpm 模块 - 确认加载吗? + 选择安装 加载 kpm 模块成功 加载 kpm 模块失败 KPM 版本 @@ -252,4 +250,12 @@ SukiSU Ultra 展望 SukiSU Ultra 未来将会成为一个相对独立的 KSU 分支,但是依然感谢官方 KernelSU 和 MKSU 等做出的贡献 个性化设置 + 安装 + 加载 + 嵌入 + 请选择模块安装模式:\n\n加载:临时加载模块\n嵌入:永久安装到系统 + 无法检查模块文件是否存在 + 确认卸载 + 删除 + 取消 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index cf19487b..d17859e7 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -232,11 +232,9 @@ releases author uninstallation - Determine the kernel module to uninstall: %1$s ? Uninstalled successfully Failed to uninstall - Load the kpm module - Confirm Load? + Select Installation Load of kpm module successful Load of kpm module failed kpm parameters @@ -255,4 +253,13 @@ Kernel not patched Kernel not configured Custom settings + install + Load + embed + Please select the module installation mode: \n\nLoad: Temporarily load the module \nEmbedded: Permanently install into the system + Failed to check module file existence + Unable to check if module file exists + Confirm uninstallation + removing + abolish