Add getInstalledPackages to Xposed module API
Expose IPackageManager.getInstalledPackages via binder transaction, allowing per-app proxy to list packages without ROOT when Xposed is active.
This commit is contained in:
@@ -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<PackageInfo>? {
|
||||
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<PackageInfo>).list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<String>()
|
||||
@@ -117,4 +128,73 @@ class HookIConnectivityManagerOnTransact(
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInstalledPackages(flags: Long, userId: Int): List<PackageInfo> {
|
||||
return binderLocalScope {
|
||||
val pm = getPackageManager() ?: return@binderLocalScope emptyList()
|
||||
getInstalledPackagesCompat(pm, flags, userId)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> 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<PackageInfo> {
|
||||
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<PackageInfo> {
|
||||
if (raw == null) return emptyList()
|
||||
if (raw is List<*>) {
|
||||
return raw.filterIsInstance<PackageInfo>()
|
||||
}
|
||||
return try {
|
||||
val method = raw.javaClass.getMethod("getList")
|
||||
val list = method.invoke(raw)
|
||||
if (list is List<*>) {
|
||||
list.filterIsInstance<PackageInfo>()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,11 @@ object PackageQueryManager {
|
||||
|
||||
suspend fun getInstalledPackages(flags: Int, retryFlags: Int): List<PackageInfo> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -62,7 +62,11 @@ object PackageQueryManager {
|
||||
|
||||
suspend fun getInstalledPackages(flags: Int, retryFlags: Int): List<PackageInfo> {
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user