You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
manager: add support for opening zip file and directly flash module
This commit is contained in:
@@ -24,6 +24,15 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:mimeType="application/zip" />
|
||||||
|
<data android:scheme="file" />
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:pathPattern=".*\\.zip" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
|
|||||||
import com.rifsxd.ksunext.ui.util.rootAvailable
|
import com.rifsxd.ksunext.ui.util.rootAvailable
|
||||||
import com.rifsxd.ksunext.ui.util.install
|
import com.rifsxd.ksunext.ui.util.install
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import com.rifsxd.ksunext.ui.screen.FlashIt
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -70,6 +74,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
if (isManager) install()
|
if (isManager) install()
|
||||||
|
|
||||||
|
// Check if launched with a ZIP file
|
||||||
|
val zipUri: Uri? = when (intent?.action) {
|
||||||
|
Intent.ACTION_VIEW, Intent.ACTION_SEND -> {
|
||||||
|
intent.data ?: intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}?.takeIf { it.toString().endsWith(".zip", ignoreCase = true) }
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
// Read AMOLED mode preference
|
// Read AMOLED mode preference
|
||||||
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
@@ -82,6 +94,18 @@ class MainActivity : ComponentActivity() {
|
|||||||
val snackBarHostState = remember { SnackbarHostState() }
|
val snackBarHostState = remember { SnackbarHostState() }
|
||||||
val currentDestination = navController.currentBackStackEntryAsState()?.value?.destination
|
val currentDestination = navController.currentBackStackEntryAsState()?.value?.destination
|
||||||
|
|
||||||
|
val navigator = navController.rememberDestinationsNavigator()
|
||||||
|
|
||||||
|
LaunchedEffect(zipUri) {
|
||||||
|
if (zipUri != null) {
|
||||||
|
navigator.navigate(
|
||||||
|
FlashScreenDestination(
|
||||||
|
FlashIt.FlashModules(listOf(zipUri))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val showBottomBar = when (currentDestination?.route) {
|
val showBottomBar = when (currentDestination?.route) {
|
||||||
FlashScreenDestination.route -> false // Hide for FlashScreenDestination
|
FlashScreenDestination.route -> false // Hide for FlashScreenDestination
|
||||||
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
|
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import com.rifsxd.ksunext.R
|
import com.rifsxd.ksunext.R
|
||||||
|
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
|
||||||
|
import com.rifsxd.ksunext.ui.component.ConfirmResult
|
||||||
import com.rifsxd.ksunext.ui.component.KeyEventBlocker
|
import com.rifsxd.ksunext.ui.component.KeyEventBlocker
|
||||||
import com.rifsxd.ksunext.ui.util.FlashResult
|
import com.rifsxd.ksunext.ui.util.FlashResult
|
||||||
import com.rifsxd.ksunext.ui.util.LkmSelection
|
import com.rifsxd.ksunext.ui.util.LkmSelection
|
||||||
@@ -138,12 +140,41 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
// Disable back button if flashing is running
|
// Disable back button if flashing is running
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
val confirmDialog = rememberConfirmDialog()
|
||||||
if (text.isNotEmpty()) {
|
var confirmed by rememberSaveable { mutableStateOf(flashIt !is FlashIt.FlashModules) }
|
||||||
return@LaunchedEffect
|
var pendingFlashIt by rememberSaveable { mutableStateOf<FlashIt?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(flashIt) {
|
||||||
|
if (flashIt is FlashIt.FlashModules && !confirmed) {
|
||||||
|
val uris = flashIt.uris
|
||||||
|
val moduleNames =
|
||||||
|
uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }
|
||||||
|
.joinToString("")
|
||||||
|
val confirmContent =
|
||||||
|
context.getString(R.string.module_install_prompt_with_name, moduleNames)
|
||||||
|
val confirmTitle = context.getString(R.string.module)
|
||||||
|
val result = confirmDialog.awaitConfirm(
|
||||||
|
title = confirmTitle,
|
||||||
|
content = confirmContent,
|
||||||
|
markdown = true
|
||||||
|
)
|
||||||
|
if (result == ConfirmResult.Confirmed) {
|
||||||
|
confirmed = true
|
||||||
|
pendingFlashIt = flashIt
|
||||||
|
} else {
|
||||||
|
// User cancelled, go back
|
||||||
|
navigator.popBackStack()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
confirmed = true
|
||||||
|
pendingFlashIt = flashIt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(confirmed, pendingFlashIt) {
|
||||||
|
if (!confirmed || pendingFlashIt == null || text.isNotEmpty()) return@LaunchedEffect
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
flashIt(flashIt, onStdout = {
|
flashIt(pendingFlashIt!!, onStdout = {
|
||||||
tempText = "$it\n"
|
tempText = "$it\n"
|
||||||
if (tempText.startsWith("[H[J")) { // clear command
|
if (tempText.startsWith("[H[J")) { // clear command
|
||||||
text = tempText.substring(6)
|
text = tempText.substring(6)
|
||||||
@@ -282,6 +313,19 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Uri.getFileName(context: Context): String {
|
||||||
|
val contentResolver = context.contentResolver
|
||||||
|
val cursor = contentResolver.query(this, null, null, null, null)
|
||||||
|
return cursor?.use {
|
||||||
|
val nameIndex = it.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
|
||||||
|
if (it.moveToFirst() && nameIndex != -1) {
|
||||||
|
it.getString(nameIndex)
|
||||||
|
} else {
|
||||||
|
this.lastPathSegment ?: "unknown.zip"
|
||||||
|
}
|
||||||
|
} ?: (this.lastPathSegment ?: "unknown.zip")
|
||||||
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
sealed class FlashIt : Parcelable {
|
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) :
|
||||||
|
|||||||
@@ -291,15 +291,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (!hideInstallButton) {
|
if (!hideInstallButton) {
|
||||||
val moduleInstall = stringResource(id = R.string.module_install)
|
val moduleInstall = stringResource(id = R.string.module_install)
|
||||||
val confirmTitle = stringResource(R.string.module)
|
|
||||||
var zipUris by remember { mutableStateOf<List<Uri>>(emptyList()) }
|
|
||||||
val confirmDialog = rememberConfirmDialog(onConfirm = {
|
|
||||||
if (viewModel.zipUris.isNotEmpty()) {
|
|
||||||
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(viewModel.zipUris)))
|
|
||||||
viewModel.clearZipUris()
|
|
||||||
viewModel.markNeedRefresh()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
val selectZipLauncher = rememberLauncherForActivityResult(
|
val selectZipLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
) { result ->
|
) { result ->
|
||||||
@@ -322,19 +313,9 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
viewModel.updateZipUris(uris)
|
viewModel.updateZipUris(uris)
|
||||||
|
|
||||||
// Show confirm dialog with selected zip file(s) name(s)
|
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(uris)))
|
||||||
val moduleNames =
|
viewModel.clearZipUris()
|
||||||
uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }
|
viewModel.markNeedRefresh()
|
||||||
.joinToString("")
|
|
||||||
val confirmContent =
|
|
||||||
context.getString(R.string.module_install_prompt_with_name, moduleNames)
|
|
||||||
zipUris = uris
|
|
||||||
confirmDialog.showConfirm(
|
|
||||||
title = confirmTitle,
|
|
||||||
content = confirmContent,
|
|
||||||
markdown = true
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
|
|||||||
Reference in New Issue
Block a user