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 androidx.compose.ui.unit.dp
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
|
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.KeyEventBlocker
|
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.LocalSnackbarHost
|
||||||
import me.weishu.kernelsu.ui.util.installBoot
|
import me.weishu.kernelsu.ui.util.installBoot
|
||||||
import me.weishu.kernelsu.ui.util.installModule
|
import me.weishu.kernelsu.ui.util.installModule
|
||||||
@@ -140,7 +142,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
sealed class FlashIt : Parcelable {
|
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()
|
data class FlashModule(val uri: Uri) : FlashIt()
|
||||||
}
|
}
|
||||||
@@ -152,8 +155,8 @@ fun flashIt(
|
|||||||
) {
|
) {
|
||||||
when (flashIt) {
|
when (flashIt) {
|
||||||
is FlashIt.FlashBoot -> installBoot(
|
is FlashIt.FlashBoot -> installBoot(
|
||||||
flashIt.bootUri,
|
flashIt.boot,
|
||||||
flashIt.lkmUri,
|
flashIt.lkm,
|
||||||
flashIt.ota,
|
flashIt.ota,
|
||||||
onFinish,
|
onFinish,
|
||||||
onStdout,
|
onStdout,
|
||||||
@@ -188,5 +191,5 @@ private fun TopBar(onBack: () -> Unit = {}, onSave: () -> Unit = {}) {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun InstallPreview() {
|
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.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
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.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
|
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||||
import me.weishu.kernelsu.R
|
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.rememberConfirmDialog
|
||||||
|
import me.weishu.kernelsu.ui.component.rememberCustomDialog
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.FlashScreenDestination
|
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.isAbDevice
|
||||||
import me.weishu.kernelsu.ui.util.isInitBoot
|
import me.weishu.kernelsu.ui.util.isInitBoot
|
||||||
import me.weishu.kernelsu.ui.util.rootAvailable
|
import me.weishu.kernelsu.ui.util.rootAvailable
|
||||||
@@ -53,24 +64,44 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
mutableStateOf<InstallMethod?>(null)
|
mutableStateOf<InstallMethod?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
var lkmFileUri = null as Uri?
|
var lkmSelection by remember {
|
||||||
|
mutableStateOf<LkmSelection>(LkmSelection.KmiNone)
|
||||||
|
}
|
||||||
|
|
||||||
val onClickInstall = {
|
val onInstall = {
|
||||||
installMethod?.let { method ->
|
installMethod?.let { method ->
|
||||||
val flashIt = FlashIt.FlashBoot(
|
val flashIt = FlashIt.FlashBoot(
|
||||||
bootUri = if (method is InstallMethod.SelectFile) method.uri else null,
|
boot = if (method is InstallMethod.SelectFile) method.uri else null,
|
||||||
lkmUri = lkmFileUri,
|
lkm = lkmSelection,
|
||||||
ota = method is InstallMethod.DirectInstallToInactiveSlot
|
ota = method is InstallMethod.DirectInstallToInactiveSlot
|
||||||
)
|
)
|
||||||
navigator.navigate(FlashScreenDestination(flashIt))
|
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 =
|
val selectLkmLauncher =
|
||||||
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
|
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == Activity.RESULT_OK) {
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
it.data?.data?.let { uri ->
|
it.data?.data?.let { uri ->
|
||||||
lkmFileUri = uri
|
lkmSelection = LkmSelection.LkmUri(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +134,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = installMethod != null,
|
enabled = installMethod != null,
|
||||||
onClick = {
|
onClick = {
|
||||||
onClickInstall()
|
onClickNext()
|
||||||
}) {
|
}) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(id = R.string.install_next),
|
stringResource(id = R.string.install_next),
|
||||||
@@ -122,12 +153,12 @@ sealed class InstallMethod {
|
|||||||
override val summary: String?
|
override val summary: String?
|
||||||
) : InstallMethod()
|
) : InstallMethod()
|
||||||
|
|
||||||
object DirectInstall : InstallMethod() {
|
data object DirectInstall : InstallMethod() {
|
||||||
override val label: Int
|
override val label: Int
|
||||||
get() = R.string.direct_install
|
get() = R.string.direct_install
|
||||||
}
|
}
|
||||||
|
|
||||||
object DirectInstallToInactiveSlot : InstallMethod() {
|
data object DirectInstallToInactiveSlot : InstallMethod() {
|
||||||
override val label: Int
|
override val label: Int
|
||||||
get() = R.string.install_inactive_slot
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(onBack: () -> Unit = {}, onLkmUpload: () -> Unit = {}) {
|
private fun TopBar(onBack: () -> Unit = {}, onLkmUpload: () -> Unit = {}) {
|
||||||
@@ -249,5 +313,5 @@ private fun TopBar(onBack: () -> Unit = {}, onLkmUpload: () -> Unit = {}) {
|
|||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun SelectInstall_Preview() {
|
fun SelectInstall_Preview() {
|
||||||
// InstallScreen(DestinationsNavigator())
|
InstallScreen(EmptyDestinationsNavigator)
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,14 @@ package me.weishu.kernelsu.ui.util
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.os.Parcelable
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.topjohnwu.superuser.CallbackList
|
import com.topjohnwu.superuser.CallbackList
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
import me.weishu.kernelsu.BuildConfig
|
import me.weishu.kernelsu.BuildConfig
|
||||||
import me.weishu.kernelsu.Natives
|
import me.weishu.kernelsu.Natives
|
||||||
import me.weishu.kernelsu.ksuApp
|
import me.weishu.kernelsu.ksuApp
|
||||||
@@ -102,10 +104,7 @@ fun uninstallModule(id: String): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun installModule(
|
fun installModule(
|
||||||
uri: Uri,
|
uri: Uri, onFinish: (Boolean) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
|
||||||
onFinish: (Boolean) -> Unit,
|
|
||||||
onStdout: (String) -> Unit,
|
|
||||||
onStderr: (String) -> Unit
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val resolver = ksuApp.contentResolver
|
val resolver = ksuApp.contentResolver
|
||||||
with(resolver.openInputStream(uri)) {
|
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(
|
fun installBoot(
|
||||||
bootUri: Uri?,
|
bootUri: Uri?,
|
||||||
lkmUri: Uri?,
|
lkm: LkmSelection,
|
||||||
ota: Boolean,
|
ota: Boolean,
|
||||||
onFinish: (Boolean) -> Unit,
|
onFinish: (Boolean) -> Unit,
|
||||||
onStdout: (String) -> 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")
|
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
|
||||||
var cmd = "boot-patch --magiskboot ${magiskboot.absolutePath}"
|
var cmd = "boot-patch --magiskboot ${magiskboot.absolutePath}"
|
||||||
|
|
||||||
@@ -187,8 +182,27 @@ fun installBoot(
|
|||||||
cmd += " -u"
|
cmd += " -u"
|
||||||
}
|
}
|
||||||
|
|
||||||
lkmFile?.let {
|
var lkmFile: File? = null
|
||||||
cmd += " -m ${it.absolutePath}"
|
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
|
// output dir
|
||||||
@@ -211,8 +225,7 @@ fun installBoot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val result =
|
val result =
|
||||||
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback)
|
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback).exec()
|
||||||
.exec()
|
|
||||||
Log.i("KernelSU", "install boot result: ${result.isSuccess}")
|
Log.i("KernelSU", "install boot result: ${result.isSuccess}")
|
||||||
|
|
||||||
bootFile?.delete()
|
bootFile?.delete()
|
||||||
@@ -259,15 +272,14 @@ fun isInitBoot(): Boolean {
|
|||||||
|
|
||||||
fun getCurrentKmi(): String {
|
fun getCurrentKmi(): String {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
val cmd = "boot-info supported-kmi"
|
val cmd = "boot-info current-kmi"
|
||||||
return ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd")
|
return ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSupportedKmis(): List<String> {
|
fun getSupportedKmis(): List<String> {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
val cmd = "boot-info supported-kmi"
|
val cmd = "boot-info supported-kmi"
|
||||||
val out =
|
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
|
||||||
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
|
|
||||||
return out.filter { it.isNotBlank() }.map { it.trim() }
|
return out.filter { it.isNotBlank() }.map { it.trim() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,9 +318,8 @@ fun getSepolicy(pkg: String): String {
|
|||||||
|
|
||||||
fun setSepolicy(pkg: String, rules: String): Boolean {
|
fun setSepolicy(pkg: String, rules: String): Boolean {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
val result =
|
val result = shell.newJob().add("${getKsuDaemonPath()} profile set-sepolicy $pkg '$rules'")
|
||||||
shell.newJob().add("${getKsuDaemonPath()} profile set-sepolicy $pkg '$rules'")
|
.to(ArrayList(), null).exec()
|
||||||
.to(ArrayList(), null).exec()
|
|
||||||
Log.i(TAG, "set sepolicy result: ${result.code}")
|
Log.i(TAG, "set sepolicy result: ${result.code}")
|
||||||
return result.isSuccess
|
return result.isSuccess
|
||||||
}
|
}
|
||||||
@@ -322,22 +333,19 @@ fun listAppProfileTemplates(): List<String> {
|
|||||||
fun getAppProfileTemplate(id: String): String {
|
fun getAppProfileTemplate(id: String): String {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
return shell.newJob().add("${getKsuDaemonPath()} profile get-template '${id}'")
|
return shell.newJob().add("${getKsuDaemonPath()} profile get-template '${id}'")
|
||||||
.to(ArrayList(), null)
|
.to(ArrayList(), null).exec().out.joinToString("\n")
|
||||||
.exec().out.joinToString("\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAppProfileTemplate(id: String, template: String): Boolean {
|
fun setAppProfileTemplate(id: String, template: String): Boolean {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
return shell.newJob().add("${getKsuDaemonPath()} profile set-template '${id}' '${template}'")
|
return shell.newJob().add("${getKsuDaemonPath()} profile set-template '${id}' '${template}'")
|
||||||
.to(ArrayList(), null)
|
.to(ArrayList(), null).exec().isSuccess
|
||||||
.exec().isSuccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAppProfileTemplate(id: String): Boolean {
|
fun deleteAppProfileTemplate(id: String): Boolean {
|
||||||
val shell = getRootShell()
|
val shell = getRootShell()
|
||||||
return shell.newJob().add("${getKsuDaemonPath()} profile delete-template '${id}'")
|
return shell.newJob().add("${getKsuDaemonPath()} profile delete-template '${id}'")
|
||||||
.to(ArrayList(), null)
|
.to(ArrayList(), null).exec().isSuccess
|
||||||
.exec().isSuccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forceStopApp(packageName: String) {
|
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_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="install_next">Next</string>
|
||||||
<string name="select_file_tip">%1$s partition image is recommended</string>
|
<string name="select_file_tip">%1$s partition image is recommended</string>
|
||||||
|
<string name="select_kmi">Select KMI</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user