diff --git a/api/android/revanced-library.api b/api/android/revanced-library.api index 1f13fef..2277c15 100644 --- a/api/android/revanced-library.api +++ b/api/android/revanced-library.api @@ -27,7 +27,9 @@ public final class app/revanced/library/ApkSigner$Signer { public final class app/revanced/library/ApkUtils { public static final field INSTANCE Lapp/revanced/library/ApkUtils; public final fun applyTo (Lapp/revanced/patcher/PatchesResult;Ljava/io/File;)V + public final fun applyToSplits (Lapp/revanced/patcher/PatchesResult;Ljava/util/Map;)V public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V + public final fun signApks (Ljava/util/Collection;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V } public final class app/revanced/library/ApkUtils$KeyStoreDetails { @@ -165,8 +167,10 @@ public abstract class app/revanced/library/installation/installer/Installer { public final class app/revanced/library/installation/installer/Installer$Apk { public fun (Ljava/io/File;Ljava/lang/String;)V public synthetic fun (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/io/File;Ljava/lang/String;Ljava/util/Map;)V public final fun getFile ()Ljava/io/File; public final fun getPackageName ()Ljava/lang/String; + public final fun getSplitFiles ()Ljava/util/Map; } public final class app/revanced/library/installation/installer/LocalInstaller : app/revanced/library/installation/installer/Installer, java/io/Closeable { @@ -230,4 +234,3 @@ public final class app/revanced/library/logging/Logger { public final fun setFormat (Ljava/lang/String;)V public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V } - diff --git a/api/jvm/revanced-library.api b/api/jvm/revanced-library.api index bcd7b0a..1a35dda 100644 --- a/api/jvm/revanced-library.api +++ b/api/jvm/revanced-library.api @@ -27,7 +27,9 @@ public final class app/revanced/library/ApkSigner$Signer { public final class app/revanced/library/ApkUtils { public static final field INSTANCE Lapp/revanced/library/ApkUtils; public final fun applyTo (Lapp/revanced/patcher/PatchesResult;Ljava/io/File;)V + public final fun applyToSplits (Lapp/revanced/patcher/PatchesResult;Ljava/util/Map;)V public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V + public final fun signApks (Ljava/util/Collection;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V } public final class app/revanced/library/ApkUtils$KeyStoreDetails { @@ -141,8 +143,10 @@ public abstract class app/revanced/library/installation/installer/Installer { public final class app/revanced/library/installation/installer/Installer$Apk { public fun (Ljava/io/File;Ljava/lang/String;)V public synthetic fun (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/io/File;Ljava/lang/String;Ljava/util/Map;)V public final fun getFile ()Ljava/io/File; public final fun getPackageName ()Ljava/lang/String; + public final fun getSplitFiles ()Ljava/util/Map; } public final class app/revanced/library/installation/installer/RootInstallation : app/revanced/library/installation/installer/Installation { @@ -176,4 +180,3 @@ public final class app/revanced/library/logging/Logger { public final fun setFormat (Ljava/lang/String;)V public static synthetic fun setFormat$default (Lapp/revanced/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V } - diff --git a/library/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstaller.kt b/library/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstaller.kt index f8472a1..aac626e 100644 --- a/library/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstaller.kt +++ b/library/src/androidMain/kotlin/app/revanced/library/installation/installer/LocalInstaller.kt @@ -10,6 +10,7 @@ import android.content.pm.PackageInstaller import android.content.pm.PackageManager import android.os.Build import androidx.core.content.ContextCompat +import app.revanced.library.installation.installer.Constants.splitFileName import app.revanced.library.installation.installer.Installer.Apk import java.io.Closeable import java.io.File @@ -57,12 +58,15 @@ class LocalInstaller( } override suspend fun install(apk: Apk) { - logger.info("Installing ${apk.file.name}") + logger.info("Installing ${apk.file.name} with ${apk.splitFiles.size} split APK(s)") val packageInstaller = context.packageManager.packageInstaller packageInstaller.openSession(packageInstaller.createSession(sessionParams)).use { session -> - session.writeApk(apk.file) + session.writeApk("base.apk", apk.file) + apk.splitFiles.forEach { (splitName, splitFile) -> + session.writeApk(splitFileName(splitName), splitFile) + } session.commit(intentSender) } } @@ -96,9 +100,9 @@ class LocalInstaller( setInstallReason(PackageManager.INSTALL_REASON_USER) } - private fun PackageInstaller.Session.writeApk(apk: File) { + private fun PackageInstaller.Session.writeApk(name: String, apk: File) { apk.inputStream().use { inputStream -> - openWrite(apk.name, 0, apk.length()).use { outputStream -> + openWrite(name, 0, apk.length()).use { outputStream -> inputStream.copyTo(outputStream, 1024 * 1024) fsync(outputStream) } diff --git a/library/src/commonMain/kotlin/app/revanced/library/ApkUtils.kt b/library/src/commonMain/kotlin/app/revanced/library/ApkUtils.kt index bea9f0f..92b4b68 100644 --- a/library/src/commonMain/kotlin/app/revanced/library/ApkUtils.kt +++ b/library/src/commonMain/kotlin/app/revanced/library/ApkUtils.kt @@ -7,6 +7,8 @@ import com.android.tools.build.apkzlib.zip.StoredEntry import com.android.tools.build.apkzlib.zip.ZFile import com.android.tools.build.apkzlib.zip.ZFileOptions import java.io.File +import java.nio.file.Files +import java.nio.file.StandardCopyOption import java.util.* import java.util.logging.Logger import kotlin.time.Duration.Companion.days @@ -37,6 +39,52 @@ object ApkUtils { ), ) + private fun getSigner( + signer: String, + keyStoreDetails: KeyStoreDetails, + ) = ApkSigner.newApkSigner( + signer, + if (keyStoreDetails.keyStore.exists()) { + readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails) + } else { + newPrivateKeyCertificatePair(PrivateKeyCertificatePairDetails(), keyStoreDetails) + }, + ) + + /** + * Applies [PatchedResources] to the given [ZFile]. + */ + private fun applyResources(targetApkZFile: ZFile, resources: PatchesResult.PatchedResources) { + // Add resources compiled by AAPT. + resources.resourcesApk?.let { resourcesApk -> + ZFile.openReadOnly(resourcesApk).use { resourcesApkZFile -> + // Delete all resources in the target APK before merging the new ones. + // This is necessary because the resources.apk renames resources. + // So unless, the old resources are deleted, there will be orphaned resources in the APK. + // It is not necessary, but for the sake of cleanliness, it is done. + targetApkZFile.entries().filter { entry -> + entry.centralDirectoryHeader.name.startsWith(RES_PREFIX) + }.forEach(StoredEntry::delete) + + targetApkZFile.mergeFrom(resourcesApkZFile) { false } + } + } + + // Add resources not compiled by AAPT. + resources.otherResources?.let { otherResources -> + targetApkZFile.addAllRecursively(otherResources) { file -> + file.relativeTo(otherResources).invariantSeparatorsPath !in resources.doNotCompress + } + } + + // Delete resources that were staged for deletion. + if (resources.deleteResources.isNotEmpty()) { + targetApkZFile.entries().filter { entry -> + entry.centralDirectoryHeader.name in resources.deleteResources + }.forEach(StoredEntry::delete) + } + } + /** * Applies the [PatchesResult] to the given [apkFile]. * @@ -57,36 +105,7 @@ object ApkUtils { dexFile.stream.close() } - resources?.let { resources -> - // Add resources compiled by AAPT. - resources.resourcesApk?.let { resourcesApk -> - ZFile.openReadOnly(resourcesApk).use { resourcesApkZFile -> - // Delete all resources in the target APK before merging the new ones. - // This is necessary because the resources.apk renames resources. - // So unless, the old resources are deleted, there will be orphaned resources in the APK. - // It is not necessary, but for the sake of cleanliness, it is done. - targetApkZFile.entries().filter { entry -> - entry.centralDirectoryHeader.name.startsWith(RES_PREFIX) - }.forEach(StoredEntry::delete) - - targetApkZFile.mergeFrom(resourcesApkZFile) { false } - } - } - - // Add resources not compiled by AAPT. - resources.otherResources?.let { otherResources -> - targetApkZFile.addAllRecursively(otherResources) { file -> - file.relativeTo(otherResources).invariantSeparatorsPath !in resources.doNotCompress - } - } - - // Delete resources that were staged for deletion. - if (resources.deleteResources.isNotEmpty()) { - targetApkZFile.entries().filter { entry -> - entry.centralDirectoryHeader.name in resources.deleteResources - }.forEach(StoredEntry::delete) - } - } + resources?.let { applyResources(targetApkZFile, it) } logger.info("Aligning APK") @@ -96,6 +115,28 @@ object ApkUtils { } } + /** + * Applies the [PatchesResult] split resources to the given split APK files. + * + * @param splitApkFiles The split APK files keyed by split name. + */ + fun PatchesResult.applyToSplits(splitApkFiles: Map) { + splitResources.forEach { (splitName, resources) -> + val splitFile = splitApkFiles[splitName] + ?: error("No split APK file found for split \"$splitName\"") + + ZFile.openReadWrite(splitFile, zFileOptions).use { targetApkZFile -> + applyResources(targetApkZFile, resources) + + logger.info("Aligning split APK \"$splitName\"") + + targetApkZFile.realign() + + logger.fine("Writing changes for split \"$splitName\"") + } + } + } + /** * Creates a new private key and certificate pair and saves it to the keystore in [keyStoreDetails]. * @@ -158,14 +199,32 @@ object ApkUtils { outputApkFile: File, signer: String, keyStoreDetails: KeyStoreDetails, - ) = ApkSigner.newApkSigner( - signer, - if (keyStoreDetails.keyStore.exists()) { - readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails) - } else { - newPrivateKeyCertificatePair(PrivateKeyCertificatePairDetails(), keyStoreDetails) - }, - ).signApk(inputApkFile, outputApkFile) + ) = getSigner(signer, keyStoreDetails).signApk(inputApkFile, outputApkFile) + + /** + * Signs multiple APK files in place using the same signer. + * + * @param apkFiles The APK files to sign. + * @param signer The name of the signer. + * @param keyStoreDetails The details for the keystore. + */ + fun signApks( + apkFiles: Collection, + signer: String, + keyStoreDetails: KeyStoreDetails, + ) { + if (apkFiles.isEmpty()) return + + val apkSigner = getSigner(signer, keyStoreDetails) + + apkFiles.forEach { apkFile -> + val signedOutputFile = File(apkFile.parentFile, "${apkFile.nameWithoutExtension}-signed.${apkFile.extension}") + + apkSigner.signApk(apkFile, signedOutputFile) + + Files.move(signedOutputFile.toPath(), apkFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + } /** * Details for a keystore. diff --git a/library/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstaller.kt b/library/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstaller.kt index f781f04..d51a471 100644 --- a/library/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstaller.kt +++ b/library/src/commonMain/kotlin/app/revanced/library/installation/installer/AdbInstaller.kt @@ -4,10 +4,17 @@ import app.revanced.library.installation.command.AdbShellCommandRunner import app.revanced.library.installation.command.ShellCommandRunner import app.revanced.library.installation.installer.Constants.GET_SDK_VERSION import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH +import app.revanced.library.installation.installer.Constants.TMP_FILE_PATH +import app.revanced.library.installation.installer.Constants.invoke +import app.revanced.library.installation.installer.Constants.splitFileName +import se.vidstige.jadb.JadbDevice import se.vidstige.jadb.JadbException +import se.vidstige.jadb.RemoteFile +import java.io.IOException import se.vidstige.jadb.managers.Package import se.vidstige.jadb.managers.PackageManager import se.vidstige.jadb.managers.PackageManager.UPDATE_OWNERSHIP +import java.nio.charset.StandardCharsets /** * [AdbInstaller] for installing and uninstalling [Apk] files using ADB. @@ -19,11 +26,12 @@ import se.vidstige.jadb.managers.PackageManager.UPDATE_OWNERSHIP class AdbInstaller( deviceSerial: String? = null, ) : Installer() { + private val device: JadbDevice private val shellCommandRunner: ShellCommandRunner private val packageManager: PackageManager init { - val device = getDevice(deviceSerial, logger) + device = getDevice(deviceSerial, logger) shellCommandRunner = AdbShellCommandRunner(device) packageManager = PackageManager(device) @@ -33,8 +41,14 @@ class AdbInstaller( override suspend fun install(apk: Apk): AdbInstallerResult { return runPackageManager { val sdkVersion = shellCommandRunner(GET_SDK_VERSION).output.toInt() - if (sdkVersion < 34) install(apk.file) - else installWithOptions(apk.file, listOf(UPDATE_OWNERSHIP)) + if (apk.splitFiles.isEmpty()) { + if (sdkVersion < 34) install(apk.file) + else installWithOptions(apk.file, listOf(UPDATE_OWNERSHIP)) + + return@runPackageManager + } + + installSplitPackage(apk, sdkVersion >= 34) } } @@ -48,11 +62,50 @@ class AdbInstaller( it.toString() == packageName }?.let { Installation(shellCommandRunner(INSTALLED_APK_PATH).output) } + private fun installSplitPackage(apk: Apk, updateOwnership: Boolean) { + logger.info("Installing ${apk.file.name} with ${apk.splitFiles.size} split APK(s)") + + val remoteFiles = buildMap { + put(apk.file, RemoteFile(TMP_FILE_PATH("base.apk"))) + apk.splitFiles.forEach { (splitName, splitFile) -> + put(splitFile, RemoteFile(TMP_FILE_PATH(splitFileName(splitName)))) + } + } + + try { + remoteFiles.forEach { (file, remoteFile) -> + device.push(file, remoteFile) + } + + val arguments = mutableListOf("install-multiple") + if (updateOwnership) { + arguments += "--update-ownership" + } + arguments += remoteFiles.values.map(RemoteFile::getPath) + + val result = device.executeShell("pm", *arguments.toTypedArray()).use { inputStream -> + inputStream.readBytes().toString(StandardCharsets.UTF_8) + } + + if (!result.contains("Success")) { + throw JadbException("Could not install ${apk.file.name}: $result") + } + } finally { + remoteFiles.values.forEach(::removeRemoteFile) + } + } + + private fun removeRemoteFile(remoteFile: RemoteFile) { + device.executeShell("rm", "-f", remoteFile.path).use { } + } + private fun runPackageManager(block: PackageManager.() -> Unit) = try { packageManager.run(block) AdbInstallerResult.Success } catch (e: JadbException) { AdbInstallerResult.Failure(e) + } catch (e: IOException) { + AdbInstallerResult.Failure(e) } } diff --git a/library/src/commonMain/kotlin/app/revanced/library/installation/installer/Constants.kt b/library/src/commonMain/kotlin/app/revanced/library/installation/installer/Constants.kt index dc2d020..6f5262c 100644 --- a/library/src/commonMain/kotlin/app/revanced/library/installation/installer/Constants.kt +++ b/library/src/commonMain/kotlin/app/revanced/library/installation/installer/Constants.kt @@ -5,58 +5,43 @@ internal object Constants { const val PLACEHOLDER = "PLACEHOLDER" const val SELINUX_CONTEXT = "u:object_r:apk_data_file:s0" - const val TMP_FILE_PATH = "/data/local/tmp/revanced.tmp" - const val MOUNT_PATH = "/data/adb/revanced/" - const val MOUNTED_APK_PATH = "$MOUNT_PATH$PLACEHOLDER.apk" + const val MOUNT_ROOT_PATH = "/data/adb/revanced" + const val PACKAGE_MOUNT_PATH = "$MOUNT_ROOT_PATH/$PLACEHOLDER" + const val MOUNTED_APK_PATH = "$PACKAGE_MOUNT_PATH/base.apk" const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh" + const val TMP_FILE_PATH = "/data/local/tmp/$PLACEHOLDER" const val EXISTS = "[[ -f $PLACEHOLDER ]] || exit 1" const val MOUNT_GREP = "grep -F $PLACEHOLDER /proc/mounts" const val DELETE = "rm -rf $PLACEHOLDER" - const val CREATE_DIR = "mkdir -p" + const val CREATE_INSTALLATION_PATH = "mkdir -p $PLACEHOLDER" const val RESTART = "am start -S $PLACEHOLDER" const val KILL = "am force-stop $PLACEHOLDER" const val INSTALLED_APK_PATH = "pm path $PLACEHOLDER" - const val CREATE_INSTALLATION_PATH = "$CREATE_DIR $MOUNT_PATH" const val GET_SDK_VERSION = "getprop ro.build.version.sdk" - const val MOUNT_APK = - "base_path=\"$MOUNTED_APK_PATH\" && " + - $$"mv $$TMP_FILE_PATH $base_path && " + - $$"chmod 644 $base_path && " + - $$"chown system:system $base_path && " + - $$"chcon $$SELINUX_CONTEXT $base_path" - const val UMOUNT = "grep $PLACEHOLDER /proc/mounts | " + $$"while read -r line; do echo $line | " + "cut -d ' ' -f 2 | " + - "sed 's/apk.*/apk/' | " + "xargs -r umount -l; done" - const val INSTALL_MOUNT_SCRIPT = - "mv $TMP_FILE_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH" - val MOUNT_SCRIPT = $$""" #!/system/bin/sh until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done until [ -d "/sdcard/Android" ]; do sleep 1; done - stock_path=$( pm path $$PLACEHOLDER | grep base | sed 's/package://g' ) + mount_dir="$$PACKAGE_MOUNT_PATH" # Make sure the app is installed. - if [ -z "$stock_path" ]; then + if [ -z "$( pm path $$PLACEHOLDER )" ]; then exit 1 fi # Unmount any existing installations to prevent multiple unnecessary mounts. $$UMOUNT - base_path="$$MOUNTED_APK_PATH" - - chcon $$SELINUX_CONTEXT $base_path - # Mount using Magisk mirror, if available. if command -v magisk >/dev/null 2>&1; then if ! MAGISKTMP="$(magisk --path 2>/dev/null)"; then @@ -66,12 +51,28 @@ internal object Constants { [ -d "$MIRROR" ] || MIRROR="" fi - mount -o bind $MIRROR$base_path $stock_path + pm path $$PLACEHOLDER | sed 's/package://g' | while read -r stock_path; do + [ -n "$stock_path" ] || continue + + apk_name="$(basename "$stock_path")" + base_path="$mount_dir/$apk_name" + + [ -f "$base_path" ] || continue + + chcon $$SELINUX_CONTEXT "$base_path" + mount -o bind "$MIRROR$base_path" "$stock_path" + done # Kill the app to force it to restart the mounted APK in case it's currently running. $$KILL """.trimIndent() + /** + * Ensures a split name has the .apk extension. + */ + fun splitFileName(splitName: String) = + if (splitName.endsWith(".apk")) splitName else "$splitName.apk" + /** * Replaces the [PLACEHOLDER] with the given [replacement]. * diff --git a/library/src/commonMain/kotlin/app/revanced/library/installation/installer/Installer.kt b/library/src/commonMain/kotlin/app/revanced/library/installation/installer/Installer.kt index da709ea..00e0a89 100644 --- a/library/src/commonMain/kotlin/app/revanced/library/installation/installer/Installer.kt +++ b/library/src/commonMain/kotlin/app/revanced/library/installation/installer/Installer.kt @@ -19,7 +19,7 @@ abstract class Installer interna /** * Installs the [Apk] file. * - * @param apk The [Apk] file. + * @param apk The base APK file and any associated split APK files. * * @return The result of the installation. */ @@ -46,8 +46,13 @@ abstract class Installer interna /** * Apk file for [Installer]. * - * @param file The [Apk] file. + * @param file The base [Apk] file. * @param packageName The package name of the [Apk] file. + * @param splitFiles Any split APK files to install or mount alongside the base APK, keyed by split name. */ - class Apk(val file: File, val packageName: String? = null) + class Apk( + val file: File, + val packageName: String? = null, + val splitFiles: Map = emptyMap(), + ) } diff --git a/library/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstaller.kt b/library/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstaller.kt index b53d9cc..5cf54b4 100644 --- a/library/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstaller.kt +++ b/library/src/commonMain/kotlin/app/revanced/library/installation/installer/RootInstaller.kt @@ -5,13 +5,14 @@ import app.revanced.library.installation.installer.Constants.CREATE_INSTALLATION import app.revanced.library.installation.installer.Constants.DELETE import app.revanced.library.installation.installer.Constants.EXISTS import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH -import app.revanced.library.installation.installer.Constants.INSTALL_MOUNT_SCRIPT import app.revanced.library.installation.installer.Constants.KILL import app.revanced.library.installation.installer.Constants.MOUNTED_APK_PATH -import app.revanced.library.installation.installer.Constants.MOUNT_APK +import app.revanced.library.installation.installer.Constants.splitFileName import app.revanced.library.installation.installer.Constants.MOUNT_GREP +import app.revanced.library.installation.installer.Constants.MOUNT_ROOT_PATH import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT_PATH +import app.revanced.library.installation.installer.Constants.PACKAGE_MOUNT_PATH import app.revanced.library.installation.installer.Constants.RESTART import app.revanced.library.installation.installer.Constants.TMP_FILE_PATH import app.revanced.library.installation.installer.Constants.UMOUNT @@ -50,23 +51,26 @@ abstract class RootInstaller internal constructor( * @throws PackageNameRequiredException If the [Apk] does not have a package name. */ override suspend fun install(apk: Apk): RootInstallerResult { - logger.info("Installing ${apk.packageName} by mounting") + logger.info("Installing ${apk.packageName} by mounting with ${apk.splitFiles.size} split APK(s)") val packageName = apk.packageName?.also { it.assertInstalled() } ?: throw PackageNameRequiredException() + val mountDirectory = PACKAGE_MOUNT_PATH(packageName) // Setup files. - apk.file.move(TMP_FILE_PATH) - CREATE_INSTALLATION_PATH().waitFor() - MOUNT_APK(packageName)().waitFor() + CREATE_INSTALLATION_PATH(MOUNT_ROOT_PATH)() + CREATE_INSTALLATION_PATH(mountDirectory)() + + moveApk(apk.file, mountDirectory, "base.apk") + apk.splitFiles.forEach { (splitName, splitFile) -> + moveApk(splitFile, mountDirectory, splitFileName(splitName)) + } // Install and run. - TMP_FILE_PATH.write(MOUNT_SCRIPT(packageName)) - INSTALL_MOUNT_SCRIPT(packageName)().waitFor() + MOUNT_SCRIPT_PATH(packageName).write(MOUNT_SCRIPT(packageName)) + "chmod +x ${MOUNT_SCRIPT_PATH(packageName)}"().waitFor() MOUNT_SCRIPT_PATH(packageName)().waitFor() RESTART(packageName)() - DELETE(TMP_FILE_PATH)() - return RootInstallerResult.SUCCESS } @@ -75,9 +79,8 @@ abstract class RootInstaller internal constructor( UMOUNT(packageName)() - DELETE(MOUNTED_APK_PATH)(packageName)() + DELETE(PACKAGE_MOUNT_PATH)(packageName)() DELETE(MOUNT_SCRIPT_PATH)(packageName)() - DELETE(TMP_FILE_PATH)() // Remove residual. KILL(packageName)() @@ -88,7 +91,7 @@ abstract class RootInstaller internal constructor( val patchedApkPath = MOUNTED_APK_PATH(packageName) val patchedApkExists = EXISTS(patchedApkPath)().exitCode == 0 - if (patchedApkExists) return null + if (!patchedApkExists) return null return RootInstallation( INSTALLED_APK_PATH(packageName)().output.ifEmpty { null }, @@ -109,6 +112,15 @@ abstract class RootInstaller internal constructor( */ protected fun File.move(targetFilePath: String) = shellCommandRunner.move(this, targetFilePath) + private fun moveApk(file: File, directory: String, outputFileName: String) { + val temporaryPath = TMP_FILE_PATH(outputFileName) + + DELETE(temporaryPath)() + file.move(temporaryPath) + "mv $temporaryPath $directory/$outputFileName"() + "chmod 644 $directory/$outputFileName && chown system:system $directory/$outputFileName && chcon ${Constants.SELINUX_CONTEXT} $directory/$outputFileName"() + } + /** * Writes the given [content] to the file. *