diff --git a/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt b/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt index 293484ca..9ce3b3ff 100644 --- a/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt +++ b/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt @@ -54,7 +54,11 @@ class RootInstaller( await() } - suspend fun execute(vararg commands: String) = getShell().newJob().add(*commands).exec() + suspend fun execute(vararg commands: String): Shell.Result { + val stdout = mutableListOf() + val stderr = mutableListOf() + return getShell().newJob().add(*commands).to(stdout, stderr).exec() + } fun hasRootAccess() = Shell.isAppGrantedRoot() ?: false @@ -108,20 +112,15 @@ class RootInstaller( unmount(packageName) stockAPK?.let { stockApp -> - pm.getPackageInfo(packageName)?.let { packageInfo -> - // TODO: get user id programmatically - 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") - } + // TODO: get user id programmatically + execute("pm uninstall -k --user 0 $packageName") - 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( "service.sh", @@ -142,7 +141,6 @@ class RootInstaller( } "$modulePath/$packageName.apk".let { apkPath -> - remoteFS.getFile(patchedAPK.absolutePath) .also { if (!it.exists()) throw Exception("File doesn't exist") } .newInputStream().use { inputStream -> @@ -173,9 +171,43 @@ class RootInstaller( const val modulesPath = "/data/adb/modules" 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, + val stderr: List +) : Exception(format(userMessage, exitCode, stdout, stderr)) { + companion object { + private fun format(message: String, exitCode: Int, stdout: List, stderr: List): 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") \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt index 236129c5..4831f632 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt @@ -377,22 +377,22 @@ class PatcherViewModel( try { 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) { 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 // If it is, unmount it first, silently if (rootInstaller.hasRootAccess() && rootInstaller.isAppMounted(packageName)) { @@ -412,16 +412,6 @@ class PatcherViewModel( 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 ?: inputFile?.let(pm::getPackageInfo)?.versionName ?: throw Exception("Failed to determine input APK version")