diff --git a/app/src/main/java/io/nekohasekai/sfa/utils/HookStatusClient.kt b/app/src/main/java/io/nekohasekai/sfa/utils/HookStatusClient.kt index da7a0da..85e98b0 100644 --- a/app/src/main/java/io/nekohasekai/sfa/utils/HookStatusClient.kt +++ b/app/src/main/java/io/nekohasekai/sfa/utils/HookStatusClient.kt @@ -1,6 +1,8 @@ package io.nekohasekai.sfa.utils import android.content.Context +import android.content.pm.PackageInfo +import io.nekohasekai.sfa.bg.ParceledListSlice import io.nekohasekai.sfa.xposed.HookStatusKeys import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -46,4 +48,19 @@ object HookStatusClient { ) } } + + fun getInstalledPackages(context: Context, flags: Long, userId: Int): List? { + val binder = ConnectivityBinderUtils.getBinder(context) ?: return null + return ConnectivityBinderUtils.withParcel { data, reply -> + data.writeInterfaceToken(HookStatusKeys.DESCRIPTOR) + data.writeLong(flags) + data.writeInt(userId) + val ok = binder.transact(HookStatusKeys.TRANSACTION_GET_INSTALLED_PACKAGES, data, reply, 0) + if (!ok) return@withParcel null + reply.readException() + val slice = ParceledListSlice.CREATOR.createFromParcel(reply, PackageInfo::class.java.classLoader) + @Suppress("UNCHECKED_CAST") + (slice as ParceledListSlice).list + } + } } diff --git a/app/src/main/java/io/nekohasekai/sfa/xposed/HookStatusKeys.kt b/app/src/main/java/io/nekohasekai/sfa/xposed/HookStatusKeys.kt index 31c337e..f8ee3cd 100644 --- a/app/src/main/java/io/nekohasekai/sfa/xposed/HookStatusKeys.kt +++ b/app/src/main/java/io/nekohasekai/sfa/xposed/HookStatusKeys.kt @@ -6,4 +6,5 @@ object HookStatusKeys { const val TRANSACTION_UPDATE_PRIVILEGE_SETTINGS = 0x5F01 const val TRANSACTION_GET_ERRORS = 0x5F02 const val TRANSACTION_EXPORT_DEBUG_INFO = 0x5F03 + const val TRANSACTION_GET_INSTALLED_PACKAGES = 0x5F04 } diff --git a/app/src/main/java/io/nekohasekai/sfa/xposed/hooks/IConnectivityManager+onTransact.kt b/app/src/main/java/io/nekohasekai/sfa/xposed/hooks/IConnectivityManager+onTransact.kt index 8581832..9f6632d 100644 --- a/app/src/main/java/io/nekohasekai/sfa/xposed/hooks/IConnectivityManager+onTransact.kt +++ b/app/src/main/java/io/nekohasekai/sfa/xposed/hooks/IConnectivityManager+onTransact.kt @@ -1,6 +1,7 @@ package io.nekohasekai.sfa.xposed.hooks import android.content.Context +import android.content.pm.PackageInfo import android.os.Binder import android.os.Parcel import de.robv.android.xposed.XposedHelpers @@ -35,7 +36,8 @@ class HookIConnectivityManagerOnTransact( val code = param.args[0] as Int if (code != HookStatusKeys.TRANSACTION_STATUS && code != HookStatusKeys.TRANSACTION_UPDATE_PRIVILEGE_SETTINGS && - code != HookStatusKeys.TRANSACTION_GET_ERRORS) { + code != HookStatusKeys.TRANSACTION_GET_ERRORS && + code != HookStatusKeys.TRANSACTION_GET_INSTALLED_PACKAGES) { return } val data = param.args[1] as Parcel @@ -72,6 +74,15 @@ class HookIConnectivityManagerOnTransact( param.result = true return } + if (code == HookStatusKeys.TRANSACTION_GET_INSTALLED_PACKAGES) { + val flags = data.readLong() + val userId = data.readInt() + val packages = getInstalledPackages(flags, userId) + reply!!.writeNoException() + ParceledListSlice(packages).writeToParcel(reply, 0) + param.result = true + return + } val enabled = data.readInt() != 0 val slice = ParceledListSlice.CREATOR.createFromParcel(data, PackageEntry::class.java.classLoader) val packages = HashSet() @@ -117,4 +128,73 @@ class HookIConnectivityManagerOnTransact( false } } + + private fun getInstalledPackages(flags: Long, userId: Int): List { + return binderLocalScope { + val pm = getPackageManager() ?: return@binderLocalScope emptyList() + getInstalledPackagesCompat(pm, flags, userId) + } + } + + private inline fun binderLocalScope(block: () -> T): T { + val token = Binder.clearCallingIdentity() + return try { + block() + } finally { + Binder.restoreCallingIdentity(token) + } + } + + private fun getPackageManager(): Any? { + return try { + val appGlobals = Class.forName("android.app.AppGlobals") + val method = appGlobals.getMethod("getPackageManager") + method.invoke(null) + } catch (e: Throwable) { + HookErrorStore.e(SOURCE, "getPackageManager failed", e) + null + } + } + + private fun getInstalledPackagesCompat(pm: Any, flags: Long, userId: Int): List { + val result = try { + val method = pm.javaClass.getMethod( + "getInstalledPackages", + Long::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + ) + method.invoke(pm, flags, userId) + } catch (_: Throwable) { + try { + val method = pm.javaClass.getMethod( + "getInstalledPackages", + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + ) + method.invoke(pm, flags.toInt(), userId) + } catch (e: Throwable) { + HookErrorStore.e(SOURCE, "getInstalledPackages failed", e) + return emptyList() + } + } + return unwrapParceledListSlice(result) + } + + private fun unwrapParceledListSlice(raw: Any?): List { + if (raw == null) return emptyList() + if (raw is List<*>) { + return raw.filterIsInstance() + } + return try { + val method = raw.javaClass.getMethod("getList") + val list = method.invoke(raw) + if (list is List<*>) { + list.filterIsInstance() + } else { + emptyList() + } + } catch (_: Throwable) { + emptyList() + } + } } diff --git a/app/src/minApi21/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt b/app/src/minApi21/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt index c7f19e9..70297b2 100644 --- a/app/src/minApi21/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt +++ b/app/src/minApi21/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt @@ -54,7 +54,11 @@ object PackageQueryManager { suspend fun getInstalledPackages(flags: Int, retryFlags: Int): List { return when (val s = strategy) { - is PackageQueryStrategy.ForcedRoot -> RootClient.getInstalledPackages(flags) + is PackageQueryStrategy.ForcedRoot -> { + val userId = android.os.Process.myUserHandle().hashCode() + HookStatusClient.getInstalledPackages(Application.application, flags.toLong(), userId) + ?: RootClient.getInstalledPackages(flags) + } is PackageQueryStrategy.UserSelected -> RootClient.getInstalledPackages(flags) is PackageQueryStrategy.Direct -> getPackagesViaPackageManager(flags, retryFlags) } diff --git a/app/src/minApi23/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt b/app/src/minApi23/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt index 2421967..7e224eb 100644 --- a/app/src/minApi23/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt +++ b/app/src/minApi23/java/io/nekohasekai/sfa/vendor/PackageQueryManager.kt @@ -62,7 +62,11 @@ object PackageQueryManager { suspend fun getInstalledPackages(flags: Int, retryFlags: Int): List { return when (val s = strategy) { - is PackageQueryStrategy.ForcedRoot -> RootClient.getInstalledPackages(flags) + is PackageQueryStrategy.ForcedRoot -> { + val userId = android.os.Process.myUserHandle().hashCode() + HookStatusClient.getInstalledPackages(Application.application, flags.toLong(), userId) + ?: RootClient.getInstalledPackages(flags) + } is PackageQueryStrategy.UserSelected -> when (s.mode) { Settings.PACKAGE_QUERY_MODE_ROOT -> RootClient.getInstalledPackages(flags) else -> ShizukuPackageManager.getInstalledPackages(flags)