manager: Add install screen

This commit is contained in:
tiann
2023-01-01 18:07:02 +08:00
parent d6dabf7b24
commit 96a3b89afd
5 changed files with 191 additions and 47 deletions

View File

@@ -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)
}

View File

@@ -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<ModuleViewModel>()
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 {

View File

@@ -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<String?> = object : CallbackList<String?>() {
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
}
}

View File

@@ -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
}
}
}

View File

@@ -20,4 +20,5 @@
<string name="module">Module</string>
<string name="uninstall">Uninstall</string>
<string name="module_install">Install</string>
<string name="install">Install</string>
</resources>