diff --git a/ackpine-plugins/android-stubs/src/main/java/android/content/pm/IPackageInstaller.java b/ackpine-plugins/android-stubs/src/main/java/android/content/pm/IPackageInstaller.java index 08f1e390f..a938d588b 100644 --- a/ackpine-plugins/android-stubs/src/main/java/android/content/pm/IPackageInstaller.java +++ b/ackpine-plugins/android-stubs/src/main/java/android/content/pm/IPackageInstaller.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.content.IntentSender; import android.os.Binder; import android.os.IBinder; import android.os.IInterface; @@ -24,8 +25,9 @@ * Stub for {@code android.content.pm.IPackageInstaller}. */ public interface IPackageInstaller extends IInterface { - void abandonSession(int sessionId); IPackageInstallerSession openSession(int sessionId); + void uninstall(String packageName, int flags, IntentSender statusReceiver, int userId); + void uninstall(String packageName, String callerPackageName, int flags, IntentSender statusReceiver, int userId); abstract class Stub extends Binder implements IPackageInstaller { public static IPackageInstaller asInterface(IBinder binder) { throw new UnsupportedOperationException(); diff --git a/ackpine-plugins/libsu/src/main/kotlin/ru/solrudev/ackpine/libsu/LibsuPlugin.kt b/ackpine-plugins/libsu/src/main/kotlin/ru/solrudev/ackpine/libsu/LibsuPlugin.kt index 86df3d4c5..aa39bdf7f 100644 --- a/ackpine-plugins/libsu/src/main/kotlin/ru/solrudev/ackpine/libsu/LibsuPlugin.kt +++ b/ackpine-plugins/libsu/src/main/kotlin/ru/solrudev/ackpine/libsu/LibsuPlugin.kt @@ -73,7 +73,7 @@ public class LibsuPlugin : PrivilegedPlugin< } /** - * Uninstall parameters for [LibsuPlugin]. Uninstall flags take effect only on Android 8.1+. + * Uninstall parameters for [LibsuPlugin]. */ public class UninstallParameters private constructor(snapshot: Snapshot) : PrivilegedUninstallParameters(snapshot) { diff --git a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PackageInstallerProxy.kt b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PackageInstallerProxy.kt index 12a6613e6..80e23699d 100644 --- a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PackageInstallerProxy.kt +++ b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PackageInstallerProxy.kt @@ -26,6 +26,7 @@ import android.content.pm.PackageInstallerHidden import android.os.Build import android.os.Handler import android.os.IBinder +import android.os.RemoteException import android.os.UserHandleHidden import androidx.annotation.RequiresApi import androidx.annotation.RequiresPermission @@ -58,6 +59,9 @@ public abstract class PackageInstallerProxy protected constructor( val targetUser = installParameters[sessionId]?.targetUser ?: uninstallParameters[sessionId]?.targetUser ?: TargetUser.CURRENT + val installerPackageName = installParameters[sessionId]?.installerPackageName + ?.ifEmpty { installerPackageName } + ?: installerPackageName val resolvedUserId = if (targetUser == TargetUser.CURRENT) { UserHandleHidden.myUserId() } else { @@ -66,7 +70,7 @@ public abstract class PackageInstallerProxy protected constructor( val packageInstaller = packageInstallers.computeIfAbsentCompat(resolvedUserId, packageInstallerLocks) { createPackageInstaller(context, remotePackageInstaller, installerPackageName, resolvedUserId) } - return BoundPackageInstaller(packageInstaller!!) + return BoundPackageInstaller(packageInstaller!!, resolvedUserId, installerPackageName) } final override fun createSession( @@ -106,7 +110,9 @@ public abstract class PackageInstallerProxy protected constructor( ) private inner class BoundPackageInstaller( - private val packageInstaller: PackageInstaller + private val packageInstaller: PackageInstaller, + private val userId: Int, + private val installerPackageName: String ) : PackageInstallerService { override val uid: Int @@ -166,21 +172,35 @@ public abstract class PackageInstallerProxy protected constructor( override fun abandonSession(sessionId: Int): Unit = packageInstaller.abandonSession(sessionId) + @Suppress("CAST_NEVER_SUCCEEDS") @RequiresPermission(anyOf = [Manifest.permission.REQUEST_DELETE_PACKAGES, Manifest.permission.DELETE_PACKAGES]) override fun uninstall(packageName: String, statusReceiver: IntentSender, ackpineSessionId: UUID) { - if (Build.VERSION.SDK_INT < 27) { - packageInstaller.uninstall(packageName, statusReceiver) - return - } - val privilegedParameters = uninstallParameters[ackpineSessionId] - var flags = 0 - if (privilegedParameters != null) { - flags = applyFlag(flags, privilegedParameters.keepData, DELETE_KEEP_DATA) - flags = applyFlag(flags, privilegedParameters.allUsers, DELETE_ALL_USERS) - flags = applyFlag(flags, privilegedParameters.systemApp, DELETE_SYSTEM_APP) + val flags = uninstallParameters[ackpineSessionId]?.toFlags() ?: 0 + when { + Build.VERSION.SDK_INT >= 27 -> (packageInstaller as PackageInstallerHidden).uninstall( + packageName, + flags, + statusReceiver + ) + + Build.VERSION.SDK_INT >= 23 -> try { + remotePackageInstaller.uninstall( + packageName, + installerPackageName, + flags, + statusReceiver, + userId + ) + } catch (e: RemoteException) { + e.rethrow() + } + + else -> try { + remotePackageInstaller.uninstall(packageName, flags, statusReceiver, userId) + } catch (e: RemoteException) { + e.rethrow() + } } - @Suppress("CAST_NEVER_SUCCEEDS") - (packageInstaller as PackageInstallerHidden).uninstall(packageName, flags, statusReceiver) } } @@ -245,10 +265,26 @@ public abstract class PackageInstallerProxy protected constructor( params.installFlags = flags } + private fun PrivilegedUninstallParameters.toFlags(): Int { + var flags = 0 + flags = applyFlag(flags, keepData, DELETE_KEEP_DATA) + flags = applyFlag(flags, allUsers, DELETE_ALL_USERS) + flags = applyFlag(flags, systemApp, DELETE_SYSTEM_APP) + return flags + } + private fun applyFlag(flags: Int, isFlagPresent: Boolean, flag: Int): Int { if (isFlagPresent) { return flags or flag } return flags and flag.inv() } + + @Suppress("NewApi", "ThrowableNotThrown") // methods are available, but were hidden before API 30 + private fun RemoteException.rethrow() { + if (Build.VERSION.SDK_INT >= 24) { + rethrowFromSystemServer() + } + rethrowAsRuntimeException() + } } \ No newline at end of file diff --git a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedInstallParameters.kt b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedInstallParameters.kt index 30c27c478..70db52cb2 100644 --- a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedInstallParameters.kt +++ b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedInstallParameters.kt @@ -57,8 +57,7 @@ public abstract class PrivilegedInstallParameters protected constructor(snapshot public val allUsers: Boolean = snapshot.allUsers /** - * Installer package for the app. Empty by default, so the calling app package name will be used. Works only on - * Android 9+. + * Installer package for the app. Empty by default, so the calling app package name will be used. */ public val installerPackageName: String = snapshot.installerPackageName diff --git a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedPlugin.kt b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedPlugin.kt index 3a96aa38a..9040ecc6f 100644 --- a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedPlugin.kt +++ b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedPlugin.kt @@ -76,25 +76,17 @@ public abstract class PrivilegedPlugin< } override fun getCapabilities(context: UninstallCapabilityContext): UninstallCapabilities { - val isPackageInstallerBased = context.uninstallerType == UninstallerType.PACKAGE_INSTALLER_BASED - val flagsStatus = if ( - isPackageInstallerBased && - context.sdkInt >= Build.VERSION_CODES.O_MR1 - ) { + val status = if (context.uninstallerType == UninstallerType.PACKAGE_INSTALLER_BASED) { CapabilityStatus.SUPPORTED } else { CapabilityStatus.UNSUPPORTED } return createUninstallCapabilities( PrivilegedUninstallCapabilities.Snapshot( - keepData = flagsStatus, - allUsers = flagsStatus, - systemApp = flagsStatus, - targetUser = if (isPackageInstallerBased) { - CapabilityStatus.SUPPORTED - } else { - CapabilityStatus.UNSUPPORTED - } + keepData = status, + allUsers = status, + systemApp = status, + targetUser = status ) ) } diff --git a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedUninstallParameters.kt b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedUninstallParameters.kt index 28f115dfe..134f094e0 100644 --- a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedUninstallParameters.kt +++ b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/PrivilegedUninstallParameters.kt @@ -22,7 +22,9 @@ import ru.solrudev.ackpine.plugability.AckpinePlugin /** * Shared uninstall parameters for privileged Ackpine plugins. */ -public abstract class PrivilegedUninstallParameters protected constructor(snapshot: Snapshot) : AckpinePlugin.Parameters { +public abstract class PrivilegedUninstallParameters protected constructor( + snapshot: Snapshot +) : AckpinePlugin.Parameters { /** * Flag parameter to indicate that you don't want to delete the package's data directory. diff --git a/ackpine-plugins/shizuku/src/main/kotlin/ru/solrudev/ackpine/shizuku/ShizukuPlugin.kt b/ackpine-plugins/shizuku/src/main/kotlin/ru/solrudev/ackpine/shizuku/ShizukuPlugin.kt index 960daf4c1..322191102 100644 --- a/ackpine-plugins/shizuku/src/main/kotlin/ru/solrudev/ackpine/shizuku/ShizukuPlugin.kt +++ b/ackpine-plugins/shizuku/src/main/kotlin/ru/solrudev/ackpine/shizuku/ShizukuPlugin.kt @@ -95,7 +95,7 @@ public class ShizukuPlugin private constructor() : PrivilegedPlugin< } /** - * Uninstall parameters for [ShizukuPlugin]. Uninstall flags take effect only on Android 8.1+. + * Uninstall parameters for [ShizukuPlugin]. */ public class UninstallParameters private constructor(snapshot: Snapshot) : PrivilegedUninstallParameters(snapshot) { diff --git a/docs/changelog.md b/docs/changelog.md index bae9c95e8..913b0d086 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,14 @@ hide: Change Log ========== +Version 0.25.1 (2026-07-20) +--------------------------- + +### Bug fixes and improvements + +- Uninstall flags for privileged (Shizuku/root) sessions now work on Android 5.0+. +- Setting a custom installer package name for privileged (Shizuku/root) install sessions now works on Android 5.0+. + Version 0.25.0 (2026-07-19) --------------------------- diff --git a/docs/guide/libsu.md b/docs/guide/libsu.md index a7f359bc3..47a2d73ce 100644 --- a/docs/guide/libsu.md +++ b/docs/guide/libsu.md @@ -200,12 +200,10 @@ Flag to indicate that this install should immediately be visible to all users. #### `installerPackageName` -Installer package for the app. Empty by default, so the calling app package name will be used. Takes effect only on Android 9+. +Installer package for the app. Empty by default, so the calling app package name will be used. ### Uninstall flags -Take effect only on Android 8.1+. - #### `keepData` Flag parameter to indicate that you don't want to delete the package's data directory. diff --git a/docs/guide/shizuku.md b/docs/guide/shizuku.md index fa4ff35e0..93630e504 100644 --- a/docs/guide/shizuku.md +++ b/docs/guide/shizuku.md @@ -223,12 +223,10 @@ Flag to indicate that this install should immediately be visible to all users. #### `installerPackageName` -Installer package for the app. Empty by default, so the calling app package name will be used. Takes effect only on Android 9+. +Installer package for the app. Empty by default, so the calling app package name will be used. ### Uninstall flags -Take effect only on Android 8.1+. - #### `keepData` Flag parameter to indicate that you don't want to delete the package's data directory. diff --git a/requirements.in b/requirements.in index 3c47b5107..1e2c0e37d 100644 --- a/requirements.in +++ b/requirements.in @@ -1 +1 @@ -zensical==0.0.47 \ No newline at end of file +zensical==0.0.51 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0e0b48f7b..411f9bddf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -251,18 +251,18 @@ tomli==2.4.1 \ --hash=sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9 \ --hash=sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049 # via zensical -zensical==0.0.47 \ - --hash=sha256:1bd94937c48a2e42b5b65b32c5075849937f23cebccaf250d249efa27266e0be \ - --hash=sha256:2ee29ff819372eaab02ca0f14ac82e804332d898c57c56ffd4d89674f3e5ff71 \ - --hash=sha256:319cf370ecc6d87da69c935c5acd6e1959dd48954766e9f954319bdb0bec5d36 \ - --hash=sha256:324f783b22cd0deed0d0f3b69e28d5380b47238a1ef0f25913b88014af2bade2 \ - --hash=sha256:4162fb8b62f38e6d9b75688c1fb87e18a1cffef5eee51d2c18b79334b548e973 \ - --hash=sha256:4702605b991bece11494a9bb318d5ba7229f00e5adcc9e6010acd58e9719686b \ - --hash=sha256:59656bf604a8b03eede4ce1a847640bab1129ab86dec2e39e5bd4b808b1802d2 \ - --hash=sha256:6f6b2de477c45284201e92301f997415f45f78493bd20f479ea9a1c86bbbbcca \ - --hash=sha256:77f08ffcc3da9ca2f972330e501927aa7e8e445bfa7b758107edc645acda266e \ - --hash=sha256:81a13a8bacadedada4847eed4aaa4a3f4ef0e5b78dbb2a2022bc6fb7e7dc9464 \ - --hash=sha256:944a309be69b11daa8bba46c61fb74f32a98b637f3e7c11135e7cc2a5eccbe32 \ - --hash=sha256:97ed2b21aba5f788fc39d1597d00938d602b4d2724ed599a1dab7548fe4f0025 \ - --hash=sha256:cdc2d84f38da809a28402eda5f2b6dbb150e14427f296c5b521e0dfc6a2e8a39 +zensical==0.0.51 \ + --hash=sha256:12529d3d3991b63820952111dc1d1edc29b2c9b3a3abb16c243bcb649631ebf2 \ + --hash=sha256:134d776afa526098e05e34713e2f577c075e57a232e01b97842bb0206716afce \ + --hash=sha256:186ef37e0eee0e969e2cfae47b1b97775e3164e2cba95c71faa4dd6ef47ed009 \ + --hash=sha256:296dc7a14aa28b81a58eb57df2d5c9c9a4b0de7e90c11d99c943354287952925 \ + --hash=sha256:3c9579809f88608e7aa2cff516fff9d267d74a843cf6088a5f4227de2f092bb5 \ + --hash=sha256:67f813a1514a90890ca86248a8d54b81b2164bcbff11a6bcf11b01e1c01a1454 \ + --hash=sha256:6fa0ecaf14f56841bfc595fa141396350c72aafbec73a016ebe3c824ed21ac72 \ + --hash=sha256:b1108eae82c6e8ffc33026f60b485c1512647a5333be4f547166b7c8877b98af \ + --hash=sha256:de25de067bedfa18f916d7f366fd64a7fbf09bfcc615b44d1ddbe3b5fe02ab49 \ + --hash=sha256:e97ab39668ae3b452c550634e921a0336443743aae5e1fe031c7bb57d049e535 \ + --hash=sha256:f5d91ce246ed930224603083cef02ae8947132fc7c52901d72015ea03526fa58 \ + --hash=sha256:f779d2d87b4bf228cf2e279bc0ae6bcf3b36a9335ff283a317d01f7c15ae46b2 \ + --hash=sha256:fb7ff4946b72168759c6af0a29cf5de4c38aebe633a83292e8cd4145b5213cc2 # via -r requirements.in diff --git a/version.json b/version.json index a49d93085..8be537a69 100644 --- a/version.json +++ b/version.json @@ -1,7 +1,7 @@ { "majorVersion": 0, "minorVersion": 25, - "patchVersion": 0, + "patchVersion": 1, "suffix": "", "isSnapshot": false } \ No newline at end of file