From 96a3b89afd76501f4e71a7622364843d6b8fc41b Mon Sep 17 00:00:00 2001 From: tiann Date: Sun, 1 Jan 2023 18:07:02 +0800 Subject: [PATCH] manager: Add install screen --- .../me/weishu/kernelsu/ui/screen/Install.kt | 115 ++++++++++++++++++ .../me/weishu/kernelsu/ui/screen/Module.kt | 15 ++- .../java/me/weishu/kernelsu/ui/util/KsuCli.kt | 66 ++++++++++ .../kernelsu/ui/viewmodel/ModuleViewModel.kt | 41 ------- manager/app/src/main/res/values/strings.xml | 1 + 5 files changed, 191 insertions(+), 47 deletions(-) create mode 100644 manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt create mode 100644 manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt new file mode 100644 index 00000000..be24c21e --- /dev/null +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt @@ -0,0 +1,115 @@ +package me.weishu.kernelsu.ui.screen + +import android.net.Uri +import android.os.Environment +import android.util.Log +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Save +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import me.weishu.kernelsu.R +import me.weishu.kernelsu.ksuApp +import me.weishu.kernelsu.ui.util.LocalSnackbarHost +import me.weishu.kernelsu.ui.util.installModule +import java.io.File +import java.text.SimpleDateFormat +import java.util.* + +/** + * @author weishu + * @date 2023/1/1. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@Destination +fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) { + + var text by remember { mutableStateOf("") } + + val snackBarHost = LocalSnackbarHost.current + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + installModule(uri) { + text += "$it\n" + } + } + } + + Scaffold( + topBar = { + TopBar( + onBack = { + navigator.popBackStack() + }, + onSave = { + scope.launch { + val format = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()) + val date = format.format(Date()) + val file = File(ksuApp.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "KernelSU_install_log_${date}.log") + file.writeText(text) + snackBarHost.showSnackbar("Log saved to ${file.absolutePath}") + } + } + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize(1f) + .padding(innerPadding) + .verticalScroll(rememberScrollState()), + ) { + Text( + modifier = Modifier.padding(8.dp), + text = text, + fontSize = MaterialTheme.typography.bodySmall.fontSize, + fontFamily = MaterialTheme.typography.bodySmall.fontFamily, + lineHeight = MaterialTheme.typography.bodySmall.lineHeight, + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) { + TopAppBar( + title = { Text(stringResource(R.string.install)) }, + navigationIcon = { + IconButton( + onClick = onBack + ) { Icon(Icons.Filled.ArrowBack, contentDescription = null) } + }, + actions = { + IconButton(onClick = onSave) { + Icon( + imageVector = Icons.Filled.Save, + contentDescription = "Localized description" + ) + } + } + ) +} + +@Preview +@Composable +fun InstallPreview() { +// InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY) +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt index 9f002024..cc2858e0 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt @@ -24,15 +24,19 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator import kotlinx.coroutines.launch import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination import me.weishu.kernelsu.ui.util.LocalSnackbarHost +import me.weishu.kernelsu.ui.util.toggleModule +import me.weishu.kernelsu.ui.util.uninstallModule import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel @OptIn(ExperimentalMaterial3Api::class) @Destination @Composable -fun ModuleScreen() { +fun ModuleScreen(navigator: DestinationsNavigator) { val viewModel = viewModel() val snackBarHost = LocalSnackbarHost.current @@ -59,9 +63,8 @@ fun ModuleScreen() { val data = it.data ?: return@rememberLauncherForActivityResult val uri = data.data ?: return@rememberLauncherForActivityResult - scope.launch { - viewModel.installModule(uri) - } + navigator.navigate(InstallScreenDestination(uri)) + Log.i("ModuleScreen", "select zip result: ${it.data}") } @@ -104,11 +107,11 @@ fun ModuleScreen() { isChecked, onUninstall = { scope.launch { - val result = viewModel.uninstallModule(module.id) + val result = uninstallModule(module.id) } }, onCheckChanged = { - val success = viewModel.toggleModule(module.id, isChecked) + val success = toggleModule(module.id, isChecked) if (success) { isChecked = it } else scope.launch { diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt new file mode 100644 index 00000000..7911a94b --- /dev/null +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt @@ -0,0 +1,66 @@ +package me.weishu.kernelsu.ui.util + +import android.net.Uri +import android.util.Log +import com.topjohnwu.superuser.CallbackList +import com.topjohnwu.superuser.ShellUtils +import me.weishu.kernelsu.ksuApp +import java.io.File + + +/** + * @author weishu + * @date 2023/1/1. + */ +private const val TAG = "KsuCli" + +fun execKsud(args: String): Boolean { + val shell = ksuApp.createRootShell() + val ksduLib = ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so" + return ShellUtils.fastCmdResult(shell, "$ksduLib $args") +} + +fun toggleModule(id: String, enable: Boolean): Boolean { + val cmd = if (enable) { + "module enable $id" + } else { + "module disable $id" + } + val result = execKsud(cmd) + Log.i(TAG, "toggle module $id result: $result") + return result +} + +fun uninstallModule(id: String) : Boolean { + val cmd = "module uninstall $id" + val result = execKsud(cmd) + Log.i(TAG, "uninstall module $id result: $result") + return result +} + +fun installModule(uri: Uri, onOutput: (String) -> Unit) : Boolean { + val resolver = ksuApp.contentResolver + with(resolver.openInputStream(uri)) { + val file = File(ksuApp.cacheDir, "module.zip") + file.outputStream().use { output -> + this?.copyTo(output) + } + val cmd = "module install ${file.absolutePath}" + + val shell = ksuApp.createRootShell() + val ksduLib = ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so" + + val callbackList: CallbackList = object : CallbackList() { + override fun onAddElement(s: String?) { + onOutput(s ?: "") + } + } + + val result = shell.newJob().add("$ksduLib $cmd").to(callbackList, callbackList).exec() + Log.i("KernelSU", "install module $uri result: $result") + + file.delete() + + return result.isSuccess + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt index d3b85893..3dfe41c1 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt @@ -77,45 +77,4 @@ class ModuleViewModel : ViewModel() { Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules") } } - - private fun execKsud(args: String): Boolean { - val shell = ksuApp.createRootShell() - val ksduLib = ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so" - return ShellUtils.fastCmdResult(shell, "$ksduLib $args") - } - - fun toggleModule(id: String, enable: Boolean): Boolean { - val cmd = if (enable) { - "module enable $id" - } else { - "module disable $id" - } - val result = execKsud(cmd) - Log.i(TAG, "toggle module $id result: $result") - return result - } - - fun uninstallModule(id: String) : Boolean { - val cmd = "module uninstall $id" - val result = execKsud(cmd) - Log.i(TAG, "uninstall module $id result: $result") - return result - } - - fun installModule(uri: Uri) : Boolean { - val resolver = ksuApp.contentResolver - with(resolver.openInputStream(uri)) { - val file = File(ksuApp.cacheDir, "module.zip") - file.outputStream().use { output -> - this?.copyTo(output) - } - val cmd = "module install ${file.absolutePath}" - val result = execKsud(cmd) - Log.i(TAG, "install module $uri result: $result") - - file.delete() - - return result - } - } } diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index d14827de..7ec26684 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -20,4 +20,5 @@ Module Uninstall Install + Install