Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import dev.chrisbanes.haze.HazeTint
import dev.chrisbanes.haze.hazeSource
import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.component.BottomBar
import me.weishu.kernelsu.ui.screen.HomePager
import me.weishu.kernelsu.ui.screen.ModulePager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ fun FlashScreen(

@Parcelize
sealed class FlashIt : Parcelable {
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean, val partition: String? = null) :
FlashIt()

data class FlashModules(val uris: List<Uri>) : FlashIt()
Expand All @@ -258,6 +258,7 @@ fun flashIt(
flashIt.boot,
flashIt.lkm,
flashIt.ota,
flashIt.partition,
onStdout,
onStderr
)
Expand Down
61 changes: 57 additions & 4 deletions manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Install.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
Expand All @@ -30,6 +33,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.selection.toggleable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
Expand All @@ -49,11 +53,15 @@ import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestin
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.ChooseKmiDialog
import me.weishu.kernelsu.ui.component.SuperDropdown
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.getAvailablePartitions
import me.weishu.kernelsu.ui.util.getCurrentKmi
import me.weishu.kernelsu.ui.util.getDefaultBootDevice
import me.weishu.kernelsu.ui.util.getDefaultPartitionName
import me.weishu.kernelsu.ui.util.getSlotSuffix
import me.weishu.kernelsu.ui.util.isAbDevice
import me.weishu.kernelsu.ui.util.isInitBoot
import me.weishu.kernelsu.ui.util.rootAvailable
import top.yukonga.miuix.kmp.basic.Button
import top.yukonga.miuix.kmp.basic.ButtonDefaults
Expand Down Expand Up @@ -92,12 +100,18 @@ fun InstallScreen(navigator: DestinationsNavigator) {
mutableStateOf<LkmSelection>(LkmSelection.KmiNone)
}

var partitionSelectionIndex by remember { mutableIntStateOf(0) }
var partitionsState by remember { mutableStateOf<List<String>>(emptyList()) }

val onInstall = {
installMethod?.let { method ->
val isOta = method is InstallMethod.DirectInstallToInactiveSlot
val partitionSelection = partitionsState.getOrNull(partitionSelectionIndex)
val flashIt = FlashIt.FlashBoot(
boot = if (method is InstallMethod.SelectFile) method.uri else null,
lkm = lkmSelection,
ota = method is InstallMethod.DirectInstallToInactiveSlot
ota = isOta,
partition = partitionSelection
)
navigator.navigate(FlashScreenDestination(flashIt)) {
launchSingleTop = true
Expand Down Expand Up @@ -182,6 +196,40 @@ fun InstallScreen(navigator: DestinationsNavigator) {
installMethod = method
}
}
AnimatedVisibility(
visible = installMethod is InstallMethod.DirectInstall || installMethod is InstallMethod.DirectInstallToInactiveSlot,
enter = expandVertically(),
exit = shrinkVertically()
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp),
) {
val isOta = installMethod is InstallMethod.DirectInstallToInactiveSlot
val suffix = produceState(initialValue = "", isOta) {
value = getSlotSuffix(isOta)
}.value
val partitions = produceState(initialValue = emptyList(), isOta) {
value = getAvailablePartitions(isOta)
}.value
val defaultDevice = produceState(initialValue = "", isOta) {
value = getDefaultBootDevice(isOta)
}.value
val displayPartitions = partitions.map { name ->
val path = "/dev/block/by-name/${name}${suffix}"
if (defaultDevice.isNotBlank() && defaultDevice == path) "$name (default)" else name
}
partitionsState = partitions
if (partitionSelectionIndex >= partitions.size) partitionSelectionIndex = 0
SuperDropdown(
items = displayPartitions,
selectedIndex = partitionSelectionIndex,
title = "${stringResource(R.string.install_select_partition)} (${suffix})",
onSelectedIndexChange = { index -> partitionSelectionIndex = index }
)
}
}
Card(
modifier = Modifier
.fillMaxWidth()
Expand Down Expand Up @@ -255,9 +303,14 @@ sealed class InstallMethod {
@Composable
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice()
val isAbDevice = produceState(initialValue = false) {
value = isAbDevice()
}.value
val defaultPartitionName = produceState(initialValue = "boot") {
value = getDefaultPartitionName(false)
}.value
val selectFileTip = stringResource(
id = R.string.select_file_tip, if (isInitBoot()) "init_boot" else "boot"
id = R.string.select_file_tip, defaultPartitionName
)
val radioOptions = mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
if (rootAvailable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.component.ConfirmResult
import me.weishu.kernelsu.ui.component.DropdownImpl
import me.weishu.kernelsu.ui.component.SearchBox
import me.weishu.kernelsu.ui.component.SearchPager
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
Expand Down Expand Up @@ -121,7 +122,6 @@ import top.yukonga.miuix.kmp.basic.Switch
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.TopAppBar
import top.yukonga.miuix.kmp.basic.rememberPullToRefreshState
import me.weishu.kernelsu.ui.component.DropdownImpl
import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.icons.useful.ImmersionMore
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
Expand Down
66 changes: 51 additions & 15 deletions manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ import android.os.Environment
import android.os.Parcelable
import android.os.SystemClock
import android.provider.OpenableColumns
import android.system.Os
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.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
Expand Down Expand Up @@ -247,6 +245,7 @@ fun installBoot(
bootUri: Uri?,
lkm: LkmSelection,
ota: Boolean,
partition: String?,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit,
): FlashResult {
Expand Down Expand Up @@ -305,6 +304,10 @@ fun installBoot(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
cmd += " -o $downloadsDir"

partition?.let { part ->
cmd += " --partition $part"
}

val result = flashWithIO("${getKsuDaemonPath()} $cmd", onStdout, onStderr)
Log.i("KernelSU", "install boot result: ${result.isSuccess}")

Expand All @@ -329,28 +332,62 @@ fun rootAvailable(): Boolean {
return shell.isRoot
}

fun isAbDevice(): Boolean {
suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) {
val shell = getRootShell()
val cmd = "boot-info current-kmi"
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd")
}

suspend fun getSupportedKmis(): List<String> = withContext(Dispatchers.IO) {
val shell = getRootShell()
val cmd = "boot-info supported-kmi"
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
out.filter { it.isNotBlank() }.map { it.trim() }
}

suspend fun isAbDevice(): Boolean = withContext(Dispatchers.IO) {
val shell = getRootShell()
val cmd = "boot-info is-ab-device"
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim().toBoolean()
}

suspend fun getDefaultBootDevice(ota: Boolean): String = withContext(Dispatchers.IO) {
val shell = getRootShell()
return ShellUtils.fastCmd(shell, "getprop ro.build.ab_update").trim().toBoolean()
val cmd = if (ota) {
"boot-info default-device --ota"
} else {
"boot-info default-device"
}
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim()
}

fun isInitBoot(): Boolean {
val shell = getRootShell();
if (shell.isRoot) {
return SuFile("/dev/block/by-name/init_boot").exists() || SuFile("/dev/block/by-name/init_boot_a").exists()
suspend fun getDefaultPartitionName(ota: Boolean): String = withContext(Dispatchers.IO) {
val shell = getRootShell()
val cmd = if (ota) {
"boot-info default-partition --ota"
} else {
"boot-info default-partition"
}
return !Os.uname().release.contains("android12-")
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim()
}

suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) {
suspend fun getSlotSuffix(ota: Boolean): String = withContext(Dispatchers.IO) {
val shell = getRootShell()
val cmd = "boot-info current-kmi"
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd")
val cmd = if (ota) {
"boot-info slot-suffix --ota"
} else {
"boot-info slot-suffix"
}
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim()
}

suspend fun getSupportedKmis(): List<String> = withContext(Dispatchers.IO) {
suspend fun getAvailablePartitions(ota: Boolean): List<String> = withContext(Dispatchers.IO) {
val shell = getRootShell()
val cmd = "boot-info supported-kmi"
val cmd = if (ota) {
"boot-info available-partitions --ota"
} else {
"boot-info available-partitions"
}
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
out.filter { it.isNotBlank() }.map { it.trim() }
}
Expand Down Expand Up @@ -429,7 +466,6 @@ fun forceStopApp(packageName: String) {
}

fun launchApp(packageName: String) {

val shell = getRootShell()
val result =
shell.newJob()
Expand Down
3 changes: 2 additions & 1 deletion manager/app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,9 @@
<string name="direct_install">直接安装(推荐)</string>
<string name="select_file">选择一个文件</string>
<string name="install_inactive_slot">安装到未使用的槽位(OTA 后)</string>
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认?</string>
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!注意只能在 OTA 更新完成后的重启之前使用。</string>
<string name="install_next">下一步</string>
<string name="install_select_partition">选择分区</string>
<string name="install_upload_lkm_file">使用本地 LKM 文件</string>
<string name="install_only_support_ko_file">仅支持选择 .ko 文件</string>
<string name="select_file_tip">建议选择 %1$s 分区镜像</string>
Expand Down
1 change: 1 addition & 0 deletions manager/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
<string name="install_inactive_slot">Install to inactive slot (After OTA)</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_select_partition">Select partition</string>
<string name="install_upload_lkm_file">Use local LKM file</string>
<string name="install_only_support_ko_file">Only .ko files are supported</string>
<string name="select_file_tip">%1$s partition image is recommended</string>
Expand Down
Loading
Loading