diff --git a/ackpine-plugins/libsu/build.gradle.kts b/ackpine-plugins/libsu/build.gradle.kts
index 5fa2824b4..9c327af63 100644
--- a/ackpine-plugins/libsu/build.gradle.kts
+++ b/ackpine-plugins/libsu/build.gradle.kts
@@ -46,5 +46,4 @@ dependencies {
implementation(projects.ackpineRuntime)
implementation(androidx.room.runtime)
implementation(libs.libsu.service)
- implementation(libs.hiddenApiBypass)
}
\ No newline at end of file
diff --git a/ackpine-plugins/libsu/src/main/kotlin/ru/solrudev/ackpine/libsu/RootPackageInstaller.kt b/ackpine-plugins/libsu/src/main/kotlin/ru/solrudev/ackpine/libsu/RootPackageInstaller.kt
index 337194105..ab1bccd75 100644
--- a/ackpine-plugins/libsu/src/main/kotlin/ru/solrudev/ackpine/libsu/RootPackageInstaller.kt
+++ b/ackpine-plugins/libsu/src/main/kotlin/ru/solrudev/ackpine/libsu/RootPackageInstaller.kt
@@ -19,11 +19,9 @@ package ru.solrudev.ackpine.libsu
import android.content.Context
import android.content.pm.IPackageInstaller
import android.content.pm.IPackageManager
-import android.os.Build
import android.os.IBinder
import android.os.ServiceManager
import androidx.annotation.RestrictTo
-import org.lsposed.hiddenapibypass.HiddenApiBypass
import ru.solrudev.ackpine.plugability.AckpinePlugin
import ru.solrudev.ackpine.privileged.PackageInstallerProxy
import java.util.UUID
@@ -48,16 +46,6 @@ internal class RootPackageInstaller(
@JvmSynthetic
internal fun create(context: Context): RootPackageInstaller {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- HiddenApiBypass.addHiddenApiExemptions(
- "Landroid/content/pm/IPackageManager",
- "Landroid/content/pm/IPackageInstaller",
- "Landroid/content/pm/IPackageInstallerSession",
- "Landroid/content/pm/PackageInstaller",
- "Landroid/os/UserHandle",
- "Landroid/os/ServiceManager"
- )
- }
val rootService = RootProxyService.bind(context)
val remotePackageManager = IPackageManager.Stub.asInterface(
RootProxyBinderWrapper(rootService, ServiceManager.getService("package"))
diff --git a/ackpine-plugins/privileged/build.gradle.kts b/ackpine-plugins/privileged/build.gradle.kts
index 874328719..c3f202a71 100644
--- a/ackpine-plugins/privileged/build.gradle.kts
+++ b/ackpine-plugins/privileged/build.gradle.kts
@@ -36,4 +36,5 @@ dependencies {
api(projects.ackpineCore)
compileOnly(projects.ackpinePlugins.androidStubs)
implementation(projects.ackpineRuntime)
+ implementation(libs.hiddenApiBypass)
}
\ No newline at end of file
diff --git a/ackpine-plugins/privileged/src/main/AndroidManifest.xml b/ackpine-plugins/privileged/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..55b3c12dd
--- /dev/null
+++ b/ackpine-plugins/privileged/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/HiddenApiExemptionsInitializer.kt b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/HiddenApiExemptionsInitializer.kt
new file mode 100644
index 000000000..feae4cef3
--- /dev/null
+++ b/ackpine-plugins/privileged/src/main/kotlin/ru/solrudev/ackpine/privileged/HiddenApiExemptionsInitializer.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2026 Ilya Fomichev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.solrudev.ackpine.privileged
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RestrictTo
+import androidx.startup.Initializer
+import org.lsposed.hiddenapibypass.HiddenApiBypass
+
+/**
+ * Initializes hidden API exemptions using `androidx.startup`.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+internal class HiddenApiExemptionsInitializer : Initializer {
+
+ override fun create(context: Context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ return
+ }
+ HiddenApiBypass.setHiddenApiExemptions(
+ "Landroid/content/pm/IPackageManager",
+ "Landroid/content/pm/IPackageInstaller",
+ "Landroid/content/pm/IPackageInstallerSession",
+ "Landroid/content/pm/PackageInstaller",
+ "Landroid/os/UserHandle",
+ "Landroid/os/ServiceManager"
+ )
+ }
+
+ override fun dependencies(): List>> {
+ return emptyList()
+ }
+}
\ No newline at end of file
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 9040ecc6f..dd2863f13 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
@@ -69,7 +69,7 @@ public abstract class PrivilegedPlugin<
requestDowngrade = isSupported,
grantAllRequestedPermissions = isSupportedOnApi(Build.VERSION_CODES.M),
allUsers = isSupported,
- installerPackageName = isSupportedOnApi(Build.VERSION_CODES.P),
+ installerPackageName = isSupported,
targetUser = isSupported
)
)
diff --git a/ackpine-plugins/shizuku/build.gradle.kts b/ackpine-plugins/shizuku/build.gradle.kts
index 476ff1a1b..8bdbd3666 100644
--- a/ackpine-plugins/shizuku/build.gradle.kts
+++ b/ackpine-plugins/shizuku/build.gradle.kts
@@ -46,5 +46,4 @@ dependencies {
implementation(projects.ackpineRuntime)
implementation(androidx.room.runtime)
implementation(libs.shizuku.api)
- implementation(libs.hiddenApiBypass)
}
\ No newline at end of file
diff --git a/ackpine-plugins/shizuku/src/main/kotlin/ru/solrudev/ackpine/shizuku/ShizukuPackageInstaller.kt b/ackpine-plugins/shizuku/src/main/kotlin/ru/solrudev/ackpine/shizuku/ShizukuPackageInstaller.kt
index a643ebb76..f8e3ff8c4 100644
--- a/ackpine-plugins/shizuku/src/main/kotlin/ru/solrudev/ackpine/shizuku/ShizukuPackageInstaller.kt
+++ b/ackpine-plugins/shizuku/src/main/kotlin/ru/solrudev/ackpine/shizuku/ShizukuPackageInstaller.kt
@@ -20,10 +20,8 @@ import android.content.Context
import android.content.pm.IPackageInstaller
import android.content.pm.IPackageManager
import android.content.pm.PackageInstaller
-import android.os.Build
import android.os.IBinder
import androidx.annotation.RestrictTo
-import org.lsposed.hiddenapibypass.HiddenApiBypass
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
@@ -56,15 +54,6 @@ internal class ShizukuPackageInstaller(
@JvmSynthetic
internal fun create(context: Context): ShizukuPackageInstaller {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- HiddenApiBypass.addHiddenApiExemptions(
- "Landroid/content/pm/IPackageManager",
- "Landroid/content/pm/IPackageInstaller",
- "Landroid/content/pm/IPackageInstallerSession",
- "Landroid/content/pm/PackageInstaller",
- "Landroid/os/UserHandle"
- )
- }
val remotePackageManager = IPackageManager.Stub.asInterface(
ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package"))
)
diff --git a/docs/changelog.md b/docs/changelog.md
index 913b0d086..3d403bf7e 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -6,6 +6,14 @@ hide:
Change Log
==========
+Version 0.25.2 (2026-07-20)
+---------------------------
+
+### Bug fixes and improvements
+
+- Fix `installerPackageName` privileged plugins capability incorrectly reported as unsupported on API < 28.
+- Set hidden API exemptions only once for all privileged plugins using AndroidX startup. On future Android versions, multiple calls to `HiddenApiBypass.setHiddenExemptions()` can result in a no-op or throw. If you have your own exemptions, disable Ackpine's privileged plugins exemptions initializer and add its exemptions list to your `setHiddenExemptions` call. See docs for details.
+
Version 0.25.1 (2026-07-20)
---------------------------
diff --git a/docs/guide/libsu.md b/docs/guide/libsu.md
index 47a2d73ce..940ab38be 100644
--- a/docs/guide/libsu.md
+++ b/docs/guide/libsu.md
@@ -161,6 +161,38 @@ Also, you can use libsu for uninstall sessions:
Plugin doesn't close the cached shell by itself to avoid interfering with other possible in-flight shell operations.
+Hidden APIs
+-----------
+
+This plugin initializes hidden Android API exemptions through AndroidX Startup using [LSPosed's HiddenApiBypass](https://github.com/LSPosed/AndroidHiddenApiBypass). In case you have your own additional exemptions:
+
+1. Disable the initializer in `AndroidManifest.xml`:
+
+ ```xml
+
+
+
+ ```
+
+2. Add this list to your `HiddenApiBypass.setHiddenApiExemptions` call:
+
+ ```kotlin
+ HiddenApiBypass.setHiddenApiExemptions(
+ "Landroid/content/pm/IPackageManager",
+ "Landroid/content/pm/IPackageInstaller",
+ "Landroid/content/pm/IPackageInstallerSession",
+ "Landroid/content/pm/PackageInstaller",
+ "Landroid/os/UserHandle",
+ "Landroid/os/ServiceManager"
+ )
+ ```
+
Plugin parameters
-----------------
diff --git a/docs/guide/shizuku.md b/docs/guide/shizuku.md
index 93630e504..f36f187f1 100644
--- a/docs/guide/shizuku.md
+++ b/docs/guide/shizuku.md
@@ -184,6 +184,38 @@ Also, you can use Shizuku for uninstall sessions:
If Shizuku service is not running, or if Shizuku permission is not granted for your app, session will fail.
+Hidden APIs
+-----------
+
+This plugin initializes hidden Android API exemptions through AndroidX Startup using [LSPosed's HiddenApiBypass](https://github.com/LSPosed/AndroidHiddenApiBypass). In case you have your own additional exemptions:
+
+1. Disable the initializer in `AndroidManifest.xml`:
+
+ ```xml
+
+
+
+ ```
+
+2. Add this list to your `HiddenApiBypass.setHiddenApiExemptions` call:
+
+ ```kotlin
+ HiddenApiBypass.setHiddenApiExemptions(
+ "Landroid/content/pm/IPackageManager",
+ "Landroid/content/pm/IPackageInstaller",
+ "Landroid/content/pm/IPackageInstallerSession",
+ "Landroid/content/pm/PackageInstaller",
+ "Landroid/os/UserHandle",
+ "Landroid/os/ServiceManager"
+ )
+ ```
+
Plugin parameters
-----------------
diff --git a/version.json b/version.json
index 8be537a69..7c06057c6 100644
--- a/version.json
+++ b/version.json
@@ -1,7 +1,7 @@
{
"majorVersion": 0,
"minorVersion": 25,
- "patchVersion": 1,
+ "patchVersion": 2,
"suffix": "",
"isSnapshot": false
}
\ No newline at end of file