manager: Expand the option to directly open the file and flash the anykernel3 kernel package
This commit is contained in:
@@ -27,6 +27,7 @@ import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationSty
|
||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
||||
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import com.sukisu.ultra.Natives
|
||||
@@ -40,8 +41,14 @@ import com.sukisu.ultra.ui.webui.initPlatform
|
||||
import com.sukisu.ultra.ui.screen.FlashIt
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import zako.zako.zako.zakoui.activity.component.BottomBar
|
||||
import zako.zako.zako.zakoui.activity.util.*
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.io.IOException
|
||||
import androidx.core.content.edit
|
||||
import com.sukisu.ultra.ui.util.rootAvailable
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var superUserViewModel: SuperUserViewModel
|
||||
@@ -113,11 +120,8 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
LaunchedEffect(zipUri) {
|
||||
if (!zipUri.isNullOrEmpty()) {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashModules(zipUri)
|
||||
)
|
||||
)
|
||||
// 检测 ZIP 文件类型并导航到相应界面
|
||||
detectZipTypeAndNavigate(zipUri, navigator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +201,115 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private enum class ZipType {
|
||||
MODULE,
|
||||
KERNEL,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
private fun detectZipType(uri: Uri): ZipType {
|
||||
return try {
|
||||
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zipStream ->
|
||||
var hasModuleProp = false
|
||||
var hasToolsFolder = false
|
||||
var hasAnykernelSh = false
|
||||
|
||||
var entry = zipStream.nextEntry
|
||||
while (entry != null) {
|
||||
val entryName = entry.name.lowercase()
|
||||
|
||||
when {
|
||||
entryName == "module.prop" || entryName.endsWith("/module.prop") -> {
|
||||
hasModuleProp = true
|
||||
}
|
||||
entryName.startsWith("tools/") || entryName == "tools" -> {
|
||||
hasToolsFolder = true
|
||||
}
|
||||
entryName == "anykernel.sh" || entryName.endsWith("/anykernel.sh") -> {
|
||||
hasAnykernelSh = true
|
||||
}
|
||||
}
|
||||
|
||||
zipStream.closeEntry()
|
||||
entry = zipStream.nextEntry
|
||||
}
|
||||
|
||||
when {
|
||||
hasModuleProp -> ZipType.MODULE
|
||||
hasToolsFolder && hasAnykernelSh -> ZipType.KERNEL
|
||||
else -> ZipType.UNKNOWN
|
||||
}
|
||||
}
|
||||
} ?: ZipType.UNKNOWN
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
ZipType.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun detectZipTypeAndNavigate(
|
||||
zipUris: ArrayList<Uri>,
|
||||
navigator: com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val moduleUris = mutableListOf<Uri>()
|
||||
val kernelUris = mutableListOf<Uri>()
|
||||
|
||||
for (uri in zipUris) {
|
||||
val zipType = detectZipType(uri)
|
||||
when (zipType) {
|
||||
ZipType.MODULE -> moduleUris.add(uri)
|
||||
ZipType.KERNEL -> kernelUris.add(uri)
|
||||
ZipType.UNKNOWN -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据检测结果导航
|
||||
withContext(Dispatchers.Main) {
|
||||
when {
|
||||
// 内核文件
|
||||
kernelUris.isNotEmpty() && moduleUris.isEmpty() -> {
|
||||
if (kernelUris.size == 1 && rootAvailable()) {
|
||||
navigator.navigate(
|
||||
InstallScreenDestination(
|
||||
preselectedKernelUri = kernelUris.first().toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
setAutoExitAfterFlash()
|
||||
}
|
||||
// 模块文件
|
||||
moduleUris.isNotEmpty() -> {
|
||||
navigator.navigate(
|
||||
FlashScreenDestination(
|
||||
FlashIt.FlashModules(ArrayList(moduleUris))
|
||||
)
|
||||
)
|
||||
setAutoExitAfterFlash()
|
||||
}
|
||||
// 如果没有识别出任何类型的文件,则直接退出
|
||||
else -> {
|
||||
(this@MainActivity as? ComponentActivity)?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
(this@MainActivity as? ComponentActivity)?.finish()
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAutoExitAfterFlash() {
|
||||
val sharedPref = getSharedPreferences("kernel_flash_prefs", MODE_PRIVATE)
|
||||
sharedPref.edit {
|
||||
putBoolean("auto_exit_after_flash", true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeViewModels() {
|
||||
superUserViewModel = SuperUserViewModel()
|
||||
homeViewModel = HomeViewModel()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
@@ -50,6 +51,7 @@ import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import androidx.core.content.edit
|
||||
|
||||
/**
|
||||
* @author ShirkNeko
|
||||
@@ -122,6 +124,11 @@ fun setModuleVerificationStatus(uri: Uri, isVerified: Boolean) {
|
||||
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val shouldAutoExit = remember {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.getBoolean("auto_exit_after_flash", false)
|
||||
}
|
||||
|
||||
// 是否通过从外部启动的模块安装
|
||||
val isExternalInstall = remember {
|
||||
when (flashIt) {
|
||||
@@ -231,10 +238,14 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
}
|
||||
hasUpdateCompleted = true
|
||||
|
||||
// 如果是外部安装的模块更新且不需要重启,延迟后自动返回
|
||||
if (isExternalInstall) {
|
||||
// 如果是外部安装或需要自动退出的模块更新且不需要重启,延迟后自动返回
|
||||
if (isExternalInstall || shouldAutoExit) {
|
||||
scope.launch {
|
||||
kotlinx.coroutines.delay(2000)
|
||||
if (shouldAutoExit) {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
||||
}
|
||||
(context as? ComponentActivity)?.finish()
|
||||
}
|
||||
}
|
||||
@@ -330,16 +341,24 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
kotlinx.coroutines.delay(500)
|
||||
navigator.navigate(FlashScreenDestination(nextFlashIt))
|
||||
}
|
||||
} else if (isExternalInstall && flashIt is FlashIt.FlashModules && flashIt.currentIndex >= flashIt.uris.size - 1) {
|
||||
// 如果是外部安装且是最后一个模块,安装完成后自动返回
|
||||
} else if ((isExternalInstall || shouldAutoExit) && flashIt is FlashIt.FlashModules && flashIt.currentIndex >= flashIt.uris.size - 1) {
|
||||
// 如果是外部安装或需要自动退出且是最后一个模块,安装完成后自动返回
|
||||
scope.launch {
|
||||
kotlinx.coroutines.delay(2000)
|
||||
if (shouldAutoExit) {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
||||
}
|
||||
(context as? ComponentActivity)?.finish()
|
||||
}
|
||||
} else if (isExternalInstall && flashIt is FlashIt.FlashModule) {
|
||||
// 如果是外部安装单个模块,安装完成后自动返回
|
||||
} else if ((isExternalInstall || shouldAutoExit) && flashIt is FlashIt.FlashModule) {
|
||||
// 如果是外部安装或需要自动退出的单个模块,安装完成后自动返回
|
||||
scope.launch {
|
||||
kotlinx.coroutines.delay(2000)
|
||||
if (shouldAutoExit) {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
||||
}
|
||||
(context as? ComponentActivity)?.finish()
|
||||
}
|
||||
}
|
||||
@@ -668,7 +687,7 @@ private fun TopBar(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getModuleNameFromUri(context: android.content.Context, uri: Uri): String {
|
||||
suspend fun getModuleNameFromUri(context: Context, uri: Uri): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (uri == Uri.EMPTY) {
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.Block
|
||||
import androidx.compose.material.icons.outlined.TaskAlt
|
||||
import androidx.compose.material.icons.outlined.Warning
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.*
|
||||
@@ -139,7 +138,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
StatusCard(
|
||||
systemStatus = viewModel.systemStatus,
|
||||
onClickInstall = {
|
||||
navigator.navigate(InstallScreenDestination)
|
||||
navigator.navigate(InstallScreenDestination(preselectedKernelUri = null))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import com.maxkeppeker.sheets.core.models.base.Header
|
||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||
import com.maxkeppeler.sheets.list.ListDialog
|
||||
@@ -71,19 +72,45 @@ enum class KpmPatchOption {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
fun InstallScreen(
|
||||
navigator: DestinationsNavigator,
|
||||
preselectedKernelUri: String? = null
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
|
||||
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
|
||||
var kpmPatchOption by remember { mutableStateOf(KpmPatchOption.FOLLOW_KERNEL) }
|
||||
val context = LocalContext.current
|
||||
var showRebootDialog by remember { mutableStateOf(false) }
|
||||
var showSlotSelectionDialog by remember { mutableStateOf(false) }
|
||||
var showKpmPatchDialog by remember { mutableStateOf(false) }
|
||||
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
|
||||
val kernelVersion = getKernelVersion()
|
||||
val isGKI = kernelVersion.isGKI()
|
||||
val isAbDevice = isAbDevice()
|
||||
val summary = stringResource(R.string.horizon_kernel_summary)
|
||||
|
||||
// 处理预选的内核文件
|
||||
LaunchedEffect(preselectedKernelUri) {
|
||||
preselectedKernelUri?.let { uriString ->
|
||||
try {
|
||||
val preselectedUri = uriString.toUri()
|
||||
val horizonMethod = InstallMethod.HorizonKernel(
|
||||
uri = preselectedUri,
|
||||
summary = summary
|
||||
)
|
||||
installMethod = horizonMethod
|
||||
tempKernelUri = preselectedUri
|
||||
if (isAbDevice) {
|
||||
showSlotSelectionDialog = true
|
||||
} else {
|
||||
showKpmPatchDialog = true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showRebootDialog) {
|
||||
RebootDialog(
|
||||
show = true,
|
||||
@@ -143,6 +170,19 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
summary = summary
|
||||
)
|
||||
installMethod = horizonMethod
|
||||
if (preselectedKernelUri != null) {
|
||||
showKpmPatchDialog = true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
KpmPatchSelectionDialog(
|
||||
show = showKpmPatchDialog,
|
||||
currentOption = kpmPatchOption,
|
||||
onDismiss = { showKpmPatchDialog = false },
|
||||
onOptionSelected = { option ->
|
||||
kpmPatchOption = option
|
||||
showKpmPatchDialog = false
|
||||
}
|
||||
)
|
||||
|
||||
@@ -194,6 +234,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
showSlotSelectionDialog = true
|
||||
} else {
|
||||
installMethod = method
|
||||
showKpmPatchDialog = true
|
||||
}
|
||||
} else {
|
||||
installMethod = method
|
||||
@@ -316,6 +357,47 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KpmPatchSelectionDialog(
|
||||
show: Boolean,
|
||||
currentOption: KpmPatchOption,
|
||||
onDismiss: () -> Unit,
|
||||
onOptionSelected: (KpmPatchOption) -> Unit
|
||||
) {
|
||||
if (show) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.kpm_patch_options)) },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.kpm_patch_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
KpmPatchOptionGroup(
|
||||
selectedOption = currentOption,
|
||||
onOptionChanged = onOptionSelected
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = { onOptionSelected(currentOption) }
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RebootDialog(
|
||||
show: Boolean,
|
||||
@@ -404,6 +486,10 @@ private fun SelectInstallMethod(
|
||||
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
|
||||
var currentSelectingMethod by remember { mutableStateOf<InstallMethod?>(null) }
|
||||
|
||||
LaunchedEffect(selectedMethod) {
|
||||
selectedOption = selectedMethod
|
||||
}
|
||||
|
||||
val selectImageLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package zako.zako.zako.zakoui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
@@ -27,6 +29,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.edit
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
@@ -73,6 +76,12 @@ fun KernelFlashScreen(
|
||||
kpmUndoPatch: Boolean = false
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val shouldAutoExit = remember {
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.getBoolean("auto_exit_after_flash", false)
|
||||
}
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
@@ -105,6 +114,16 @@ fun KernelFlashScreen(
|
||||
val onFlashComplete = {
|
||||
showFloatAction = true
|
||||
KernelFlashStateHolder.isFlashing = false
|
||||
|
||||
// 如果需要自动退出,延迟3秒后退出
|
||||
if (shouldAutoExit) {
|
||||
scope.launch {
|
||||
delay(3000)
|
||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
||||
(context as? ComponentActivity)?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始刷写
|
||||
@@ -165,6 +184,17 @@ fun KernelFlashScreen(
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
KernelFlashStateHolder.currentState = null
|
||||
KernelFlashStateHolder.currentUri = null
|
||||
KernelFlashStateHolder.currentSlot = null
|
||||
KernelFlashStateHolder.currentKpmPatchEnabled = false
|
||||
KernelFlashStateHolder.currentKpmUndoPatch = false
|
||||
KernelFlashStateHolder.isFlashing = false
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = true) {
|
||||
onBack()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user