feat: Improve root installation

This commit is contained in:
Robert
2025-12-26 01:07:58 +01:00
parent c436a7a100
commit ff70a77afb
2 changed files with 60 additions and 38 deletions

View File

@@ -54,7 +54,11 @@ class RootInstaller(
await() await()
} }
suspend fun execute(vararg commands: String) = getShell().newJob().add(*commands).exec() suspend fun execute(vararg commands: String): Shell.Result {
val stdout = mutableListOf<String>()
val stderr = mutableListOf<String>()
return getShell().newJob().add(*commands).to(stdout, stderr).exec()
}
fun hasRootAccess() = Shell.isAppGrantedRoot() ?: false fun hasRootAccess() = Shell.isAppGrantedRoot() ?: false
@@ -108,20 +112,15 @@ class RootInstaller(
unmount(packageName) unmount(packageName)
stockAPK?.let { stockApp -> stockAPK?.let { stockApp ->
pm.getPackageInfo(packageName)?.let { packageInfo -> // TODO: get user id programmatically
// TODO: get user id programmatically execute("pm uninstall -k --user 0 $packageName")
if (pm.getVersionCode(packageInfo) <= pm.getVersionCode(
pm.getPackageInfo(patchedAPK)
?: error("Failed to get package info for patched app")
)
)
execute("pm uninstall -k --user 0 $packageName").assertSuccess("Failed to uninstall stock app")
}
execute("pm install \"${stockApp.absolutePath}\"").assertSuccess("Failed to install stock app") execute("pm install -r -d --user 0 \"${stockApp.absolutePath}\"")
.assertSuccess("Failed to install stock app")
} }
remoteFS.getFile(modulePath).mkdir() remoteFS.getFile(modulePath).mkdirs()
.also { if (!it) throw Exception("Failed to create module directory") }
listOf( listOf(
"service.sh", "service.sh",
@@ -142,7 +141,6 @@ class RootInstaller(
} }
"$modulePath/$packageName.apk".let { apkPath -> "$modulePath/$packageName.apk".let { apkPath ->
remoteFS.getFile(patchedAPK.absolutePath) remoteFS.getFile(patchedAPK.absolutePath)
.also { if (!it.exists()) throw Exception("File doesn't exist") } .also { if (!it.exists()) throw Exception("File doesn't exist") }
.newInputStream().use { inputStream -> .newInputStream().use { inputStream ->
@@ -173,9 +171,43 @@ class RootInstaller(
const val modulesPath = "/data/adb/modules" const val modulesPath = "/data/adb/modules"
private fun Shell.Result.assertSuccess(errorMessage: String) { private fun Shell.Result.assertSuccess(errorMessage: String) {
if (!isSuccess) throw Exception(errorMessage) if (!isSuccess) {
throw ShellCommandException(
errorMessage,
code,
out,
err
)
}
} }
} }
} }
class ShellCommandException(
val userMessage: String,
val exitCode: Int,
val stdout: List<String>,
val stderr: List<String>
) : Exception(format(userMessage, exitCode, stdout, stderr)) {
companion object {
private fun format(message: String, exitCode: Int, stdout: List<String>, stderr: List<String>): String =
buildString {
appendLine(message)
appendLine("Exit code: $exitCode")
val output = stdout.filter { it.isNotBlank() }
val errors = stderr.filter { it.isNotBlank() }
if (output.isNotEmpty()) {
appendLine("stdout:")
output.forEach(::appendLine)
}
if (errors.isNotEmpty()) {
appendLine("stderr:")
errors.forEach(::appendLine)
}
}
}
}
class RootServiceException : Exception("Root not available") class RootServiceException : Exception("Root not available")

View File

@@ -377,22 +377,22 @@ class PatcherViewModel(
try { try {
isInstalling = true isInstalling = true
val currentPackageInfo = pm.getPackageInfo(outputFile)
?: throw Exception("Failed to load application info")
// If the app is currently installed
val existingPackageInfo = pm.getPackageInfo(currentPackageInfo.packageName)
if (existingPackageInfo != null) {
// Check if the app version is less than the installed version
if (pm.getVersionCode(currentPackageInfo) < pm.getVersionCode(existingPackageInfo)) {
// Exit if the selected app version is less than the installed version
packageInstallerStatus = PackageInstaller.STATUS_FAILURE_CONFLICT
return@launch
}
}
when (installType) { when (installType) {
InstallType.DEFAULT -> { InstallType.DEFAULT -> {
val currentPackageInfo = pm.getPackageInfo(outputFile)
?: throw Exception("Failed to load application info")
// If the app is currently installed
val existingPackageInfo = pm.getPackageInfo(currentPackageInfo.packageName)
if (existingPackageInfo != null) {
// Check if the app version is less than the installed version
if (pm.getVersionCode(currentPackageInfo) < pm.getVersionCode(existingPackageInfo)) {
// Exit if the selected app version is less than the installed version
packageInstallerStatus = PackageInstaller.STATUS_FAILURE_CONFLICT
return@launch
}
}
// Check if the app is mounted as root // Check if the app is mounted as root
// If it is, unmount it first, silently // If it is, unmount it first, silently
if (rootInstaller.hasRootAccess() && rootInstaller.isAppMounted(packageName)) { if (rootInstaller.hasRootAccess() && rootInstaller.isAppMounted(packageName)) {
@@ -412,16 +412,6 @@ class PatcherViewModel(
packageInfo.label() packageInfo.label()
} }
// Check for base APK, first check if the app is already installed
if (existingPackageInfo == null) {
// If the app is not installed, check if the output file is a base apk
if (currentPackageInfo.splitNames.isNotEmpty()) {
// Exit if there is no base APK package
packageInstallerStatus = PackageInstaller.STATUS_FAILURE_INVALID
return@launch
}
}
val inputVersion = input.selectedApp.version val inputVersion = input.selectedApp.version
?: inputFile?.let(pm::getPackageInfo)?.versionName ?: inputFile?.let(pm::getPackageInfo)?.versionName
?: throw Exception("Failed to determine input APK version") ?: throw Exception("Failed to determine input APK version")