manager: replace Runtime.exec with getRootShell for command execution
This commit is contained in:
@@ -5,11 +5,8 @@ import android.content.Intent
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.*
|
|
||||||
import androidx.compose.animation.core.*
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -19,7 +16,6 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -27,7 +23,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.sukisu.ultra.ui.component.*
|
import com.sukisu.ultra.ui.component.*
|
||||||
@@ -37,13 +32,10 @@ import com.sukisu.ultra.ui.util.*
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.net.*
|
import java.net.*
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KPM 管理界面
|
* KPM 管理界面
|
||||||
@@ -54,7 +46,6 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
|||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
fun KpmScreen(
|
fun KpmScreen(
|
||||||
navigator: DestinationsNavigator,
|
|
||||||
viewModel: KpmViewModel = viewModel()
|
viewModel: KpmViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -92,18 +83,17 @@ fun KpmScreen(
|
|||||||
LaunchedEffect(tempFileForInstall) {
|
LaunchedEffect(tempFileForInstall) {
|
||||||
tempFileForInstall?.let { tempFile ->
|
tempFileForInstall?.let { tempFile ->
|
||||||
try {
|
try {
|
||||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
|
val shell = getRootShell()
|
||||||
val process = Runtime.getRuntime().exec(command)
|
val command = "strings ${tempFile.absolutePath} | grep 'name='"
|
||||||
val inputStream = process.inputStream
|
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
|
||||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
if (result.isSuccess) {
|
||||||
var line: String?
|
for (line in result.out) {
|
||||||
while (reader.readLine().also { line = it } != null) {
|
if (line.startsWith("name=")) {
|
||||||
if (line!!.startsWith("name=")) {
|
moduleName = line.substringAfter("name=").trim()
|
||||||
moduleName = line.substringAfter("name=").trim()
|
break
|
||||||
break
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.waitFor()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("KsuCli", "Failed to get module name: ${e.message}", e)
|
Log.e("KsuCli", "Failed to get module name: ${e.message}", e)
|
||||||
}
|
}
|
||||||
@@ -434,18 +424,17 @@ private suspend fun handleModuleInstall(
|
|||||||
) {
|
) {
|
||||||
var moduleId: String? = null
|
var moduleId: String? = null
|
||||||
try {
|
try {
|
||||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
|
val shell = getRootShell()
|
||||||
val process = Runtime.getRuntime().exec(command)
|
val command = "strings ${tempFile.absolutePath} | grep 'name='"
|
||||||
val inputStream = process.inputStream
|
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
|
||||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
if (result.isSuccess) {
|
||||||
var line: String?
|
for (line in result.out) {
|
||||||
while (reader.readLine().also { line = it } != null) {
|
if (line.startsWith("name=")) {
|
||||||
if (line!!.startsWith("name=")) {
|
moduleId = line.substringAfter("name=").trim()
|
||||||
moduleId = line.substringAfter("name=").trim()
|
break
|
||||||
break
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.waitFor()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("KsuCli", "Failed to get module ID from strings command: ${e.message}", e)
|
Log.e("KsuCli", "Failed to get module ID from strings command: ${e.message}", e)
|
||||||
}
|
}
|
||||||
@@ -464,8 +453,9 @@ private suspend fun handleModuleInstall(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (isEmbed) {
|
if (isEmbed) {
|
||||||
Runtime.getRuntime().exec(arrayOf("su", "-c", "mkdir -p /data/adb/kpm")).waitFor()
|
val shell = getRootShell()
|
||||||
Runtime.getRuntime().exec(arrayOf("su", "-c", "cp ${tempFile.absolutePath} $targetPath")).waitFor()
|
shell.newJob().add("mkdir -p /data/adb/kpm").exec()
|
||||||
|
shell.newJob().add("cp ${tempFile.absolutePath} $targetPath").exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
val loadResult = loadKpmModule(tempFile.absolutePath)
|
val loadResult = loadKpmModule(tempFile.absolutePath)
|
||||||
@@ -509,8 +499,9 @@ private suspend fun handleModuleUninstall(
|
|||||||
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
|
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
|
||||||
|
|
||||||
val fileExists = try {
|
val fileExists = try {
|
||||||
val result = Runtime.getRuntime().exec(arrayOf("su", "-c", "ls /data/adb/kpm/$moduleFileName")).waitFor() == 0
|
val shell = getRootShell()
|
||||||
result
|
val result = shell.newJob().add("ls /data/adb/kpm/$moduleFileName").exec()
|
||||||
|
result.isSuccess
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("KsuCli", "Failed to check module file existence: ${e.message}", e)
|
Log.e("KsuCli", "Failed to check module file existence: ${e.message}", e)
|
||||||
snackBarHost.showSnackbar(
|
snackBarHost.showSnackbar(
|
||||||
@@ -519,6 +510,7 @@ private suspend fun handleModuleUninstall(
|
|||||||
)
|
)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
val confirmResult = confirmDialog.awaitConfirm(
|
val confirmResult = confirmDialog.awaitConfirm(
|
||||||
title = confirmTitle,
|
title = confirmTitle,
|
||||||
content = confirmContent,
|
content = confirmContent,
|
||||||
@@ -539,7 +531,8 @@ private suspend fun handleModuleUninstall(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
Runtime.getRuntime().exec(arrayOf("su", "-c", "rm $moduleFilePath")).waitFor()
|
val shell = getRootShell()
|
||||||
|
shell.newJob().add("rm $moduleFilePath").exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.fetchModuleList()
|
viewModel.fetchModuleList()
|
||||||
@@ -710,29 +703,31 @@ private fun KpmModuleItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkStringsCommand(tempFile: File): Int {
|
private fun checkStringsCommand(tempFile: File): Int {
|
||||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep -E 'name=|version=|license=|author='")
|
val shell = getRootShell()
|
||||||
val process = Runtime.getRuntime().exec(command)
|
val command = "strings ${tempFile.absolutePath} | grep -E 'name=|version=|license=|author='"
|
||||||
val inputStream = process.inputStream
|
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
|
||||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
|
||||||
var line: String?
|
if (!result.isSuccess) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
var matchCount = 0
|
var matchCount = 0
|
||||||
val keywords = listOf("name=", "version=", "license=", "author=")
|
val keywords = listOf("name=", "version=", "license=", "author=")
|
||||||
var nameExists = false
|
var nameExists = false
|
||||||
|
|
||||||
while (reader.readLine().also { line = it } != null) {
|
for (line in result.out) {
|
||||||
if (!nameExists && line!!.startsWith("name=")) {
|
if (!nameExists && line.startsWith("name=")) {
|
||||||
nameExists = true
|
nameExists = true
|
||||||
matchCount++
|
matchCount++
|
||||||
} else if (nameExists) {
|
} else if (nameExists) {
|
||||||
for (keyword in keywords) {
|
for (keyword in keywords) {
|
||||||
if (line!!.startsWith(keyword)) {
|
if (line.startsWith(keyword)) {
|
||||||
matchCount++
|
matchCount++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.waitFor()
|
|
||||||
|
|
||||||
return if (nameExists) matchCount else 0
|
return if (nameExists) matchCount else 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||||
import com.sukisu.ultra.ui.util.listModules
|
import com.sukisu.ultra.ui.util.listModules
|
||||||
|
import com.sukisu.ultra.ui.util.getRootShell
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -405,14 +404,12 @@ class ModuleSizeCache(context: Context) {
|
|||||||
*/
|
*/
|
||||||
private fun calculateModuleFolderSize(dirId: String): Long {
|
private fun calculateModuleFolderSize(dirId: String): Long {
|
||||||
return try {
|
return try {
|
||||||
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "du -sb /data/adb/modules/$dirId"))
|
val shell = getRootShell()
|
||||||
val reader = BufferedReader(InputStreamReader(process.inputStream))
|
val command = "du -sb /data/adb/modules/$dirId"
|
||||||
val output = reader.readLine()
|
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
|
||||||
process.waitFor()
|
|
||||||
reader.close()
|
|
||||||
|
|
||||||
if (output != null) {
|
if (result.isSuccess && result.out.isNotEmpty()) {
|
||||||
val sizeStr = output.split("\t").firstOrNull()
|
val sizeStr = result.out.firstOrNull()?.split("\t")?.firstOrNull()
|
||||||
sizeStr?.toLongOrNull() ?: 0L
|
sizeStr?.toLongOrNull() ?: 0L
|
||||||
} else {
|
} else {
|
||||||
0L
|
0L
|
||||||
|
|||||||
Reference in New Issue
Block a user