manager: support choose kmi manually. close #1496

This commit is contained in:
weishu
2024-03-23 12:30:27 +08:00
parent e124aab76a
commit 98030ee1ae
4 changed files with 121 additions and 45 deletions

View File

@@ -25,12 +25,14 @@ 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 com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.installBoot
import me.weishu.kernelsu.ui.util.installModule
@@ -140,7 +142,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
@Parcelize
sealed class FlashIt : Parcelable {
data class FlashBoot(val bootUri: Uri? = null, val lkmUri: Uri? = null, val ota: Boolean) : FlashIt()
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
FlashIt()
data class FlashModule(val uri: Uri) : FlashIt()
}
@@ -152,8 +155,8 @@ fun flashIt(
) {
when (flashIt) {
is FlashIt.FlashBoot -> installBoot(
flashIt.bootUri,
flashIt.lkmUri,
flashIt.boot,
flashIt.lkm,
flashIt.ota,
onFinish,
onStdout,
@@ -188,5 +191,5 @@ private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
@Preview
@Composable
fun InstallPreview() {
// InstallScreen(DestinationsNavigator(), uri = Uri.EMPTY)
InstallScreen(EmptyDestinationsNavigator)
}

View File

@@ -33,11 +33,22 @@ 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.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.DialogHandle
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.getCurrentKmi
import me.weishu.kernelsu.ui.util.getSupportedKmis
import me.weishu.kernelsu.ui.util.isAbDevice
import me.weishu.kernelsu.ui.util.isInitBoot
import me.weishu.kernelsu.ui.util.rootAvailable
@@ -53,24 +64,44 @@ fun InstallScreen(navigator: DestinationsNavigator) {
mutableStateOf<InstallMethod?>(null)
}
var lkmFileUri = null as Uri?
var lkmSelection by remember {
mutableStateOf<LkmSelection>(LkmSelection.KmiNone)
}
val onClickInstall = {
val onInstall = {
installMethod?.let { method ->
val flashIt = FlashIt.FlashBoot(
bootUri = if (method is InstallMethod.SelectFile) method.uri else null,
lkmUri = lkmFileUri,
boot = if (method is InstallMethod.SelectFile) method.uri else null,
lkm = lkmSelection,
ota = method is InstallMethod.DirectInstallToInactiveSlot
)
navigator.navigate(FlashScreenDestination(flashIt))
}
}
val currentKmi = remember { getCurrentKmi() }
val selectKmiDialog = rememberSelectKmiDialog { kmi ->
kmi?.let {
lkmSelection = LkmSelection.KmiString(it)
onInstall()
}
}
val onClickNext = {
if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
// no lkm file selected and cannot get current kmi
selectKmiDialog.show()
} else {
onInstall()
}
}
val selectLkmLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
lkmFileUri = uri
lkmSelection = LkmSelection.LkmUri(uri)
}
}
}
@@ -103,7 +134,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null,
onClick = {
onClickInstall()
onClickNext()
}) {
Text(
stringResource(id = R.string.install_next),
@@ -122,12 +153,12 @@ sealed class InstallMethod {
override val summary: String?
) : InstallMethod()
object DirectInstall : InstallMethod() {
data object DirectInstall : InstallMethod() {
override val label: Int
get() = R.string.direct_install
}
object DirectInstallToInactiveSlot : InstallMethod() {
data object DirectInstallToInactiveSlot : InstallMethod() {
override val label: Int
get() = R.string.install_inactive_slot
}
@@ -228,6 +259,39 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
return rememberCustomDialog { dismiss ->
val kmis = remember {
getSupportedKmis()
}
val options = kmis.map { value ->
ListOption(
titleText = value
)
}
var selection: String? = null
ListDialog(
state = rememberUseCaseState(visible = true, onFinishedRequest = {
onSelected(selection)
}, onCloseRequest = {
dismiss()
}),
header = Header.Default(
title = stringResource(R.string.select_kmi),
),
selection = ListSelection.Single(
showRadioButtons = true,
options = options,
) { _, option ->
selection = option.titleText
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(onBack: () -> Unit = {}, onLkmUpload: () -> Unit = {}) {
@@ -249,5 +313,5 @@ private fun TopBar(onBack: () -> Unit = {}, onLkmUpload: () -> Unit = {}) {
@Composable
@Preview
fun SelectInstall_Preview() {
// InstallScreen(DestinationsNavigator())
InstallScreen(EmptyDestinationsNavigator)
}

View File

@@ -3,12 +3,14 @@ package me.weishu.kernelsu.ui.util
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.Parcelable
import android.os.SystemClock
import android.util.Log
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.io.SuFile
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
@@ -102,10 +104,7 @@ fun uninstallModule(id: String): Boolean {
}
fun installModule(
uri: Uri,
onFinish: (Boolean) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
uri: Uri, onFinish: (Boolean) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
): Boolean {
val resolver = ksuApp.contentResolver
with(resolver.openInputStream(uri)) {
@@ -141,9 +140,16 @@ fun installModule(
}
}
@Parcelize
sealed class LkmSelection: Parcelable {
data class LkmUri(val uri: Uri) : LkmSelection()
data class KmiString(val value: String) : LkmSelection()
data object KmiNone : LkmSelection()
}
fun installBoot(
bootUri: Uri?,
lkmUri: Uri?,
lkm: LkmSelection,
ota: Boolean,
onFinish: (Boolean) -> Unit,
onStdout: (String) -> Unit,
@@ -162,17 +168,6 @@ fun installBoot(
}
}
val lkmFile = lkmUri?.let { uri ->
with(resolver.openInputStream(uri)) {
val lkmFile = File(ksuApp.cacheDir, "kernelsu-tmp-lkm.ko")
lkmFile.outputStream().use { output ->
this?.copyTo(output)
}
lkmFile
}
}
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
var cmd = "boot-patch --magiskboot ${magiskboot.absolutePath}"
@@ -187,8 +182,27 @@ fun installBoot(
cmd += " -u"
}
lkmFile?.let {
cmd += " -m ${it.absolutePath}"
var lkmFile: File? = null
when (lkm) {
is LkmSelection.LkmUri -> {
lkmFile = with(resolver.openInputStream(lkm.uri)) {
val file = File(ksuApp.cacheDir, "kernelsu-tmp-lkm.ko")
file.outputStream().use { output ->
this?.copyTo(output)
}
file
}
cmd += " -m ${lkmFile.absolutePath}"
}
is LkmSelection.KmiString -> {
cmd += " --kmi ${lkm.value}"
}
LkmSelection.KmiNone -> {
// do nothing
}
}
// output dir
@@ -211,8 +225,7 @@ fun installBoot(
}
val result =
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback)
.exec()
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback).exec()
Log.i("KernelSU", "install boot result: ${result.isSuccess}")
bootFile?.delete()
@@ -259,15 +272,14 @@ fun isInitBoot(): Boolean {
fun getCurrentKmi(): String {
val shell = getRootShell()
val cmd = "boot-info supported-kmi"
val cmd = "boot-info current-kmi"
return ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd")
}
fun getSupportedKmis(): List<String> {
val shell = getRootShell()
val cmd = "boot-info supported-kmi"
val out =
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
return out.filter { it.isNotBlank() }.map { it.trim() }
}
@@ -306,8 +318,7 @@ fun getSepolicy(pkg: String): String {
fun setSepolicy(pkg: String, rules: String): Boolean {
val shell = getRootShell()
val result =
shell.newJob().add("${getKsuDaemonPath()} profile set-sepolicy $pkg '$rules'")
val result = shell.newJob().add("${getKsuDaemonPath()} profile set-sepolicy $pkg '$rules'")
.to(ArrayList(), null).exec()
Log.i(TAG, "set sepolicy result: ${result.code}")
return result.isSuccess
@@ -322,22 +333,19 @@ fun listAppProfileTemplates(): List<String> {
fun getAppProfileTemplate(id: String): String {
val shell = getRootShell()
return shell.newJob().add("${getKsuDaemonPath()} profile get-template '${id}'")
.to(ArrayList(), null)
.exec().out.joinToString("\n")
.to(ArrayList(), null).exec().out.joinToString("\n")
}
fun setAppProfileTemplate(id: String, template: String): Boolean {
val shell = getRootShell()
return shell.newJob().add("${getKsuDaemonPath()} profile set-template '${id}' '${template}'")
.to(ArrayList(), null)
.exec().isSuccess
.to(ArrayList(), null).exec().isSuccess
}
fun deleteAppProfileTemplate(id: String): Boolean {
val shell = getRootShell()
return shell.newJob().add("${getKsuDaemonPath()} profile delete-template '${id}'")
.to(ArrayList(), null)
.exec().isSuccess
.to(ArrayList(), null).exec().isSuccess
}
fun forceStopApp(packageName: String) {

View File

@@ -116,4 +116,5 @@
<string name="install_inactive_slot_warning">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
<string name="install_next">Next</string>
<string name="select_file_tip">%1$s partition image is recommended</string>
<string name="select_kmi">Select KMI</string>
</resources>