manager: support choose kmi manually. close #1496
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user