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
|
package io.nekohasekai.sfa.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import io.nekohasekai.sfa.bg.ParceledListSlice
|
||||||
import io.nekohasekai.sfa.xposed.HookStatusKeys
|
import io.nekohasekai.sfa.xposed.HookStatusKeys
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
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_UPDATE_PRIVILEGE_SETTINGS = 0x5F01
|
||||||
const val TRANSACTION_GET_ERRORS = 0x5F02
|
const val TRANSACTION_GET_ERRORS = 0x5F02
|
||||||
const val TRANSACTION_EXPORT_DEBUG_INFO = 0x5F03
|
const val TRANSACTION_EXPORT_DEBUG_INFO = 0x5F03
|
||||||
|
const val TRANSACTION_GET_INSTALLED_PACKAGES = 0x5F04
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.nekohasekai.sfa.xposed.hooks
|
package io.nekohasekai.sfa.xposed.hooks
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
@@ -35,7 +36,8 @@ class HookIConnectivityManagerOnTransact(
|
|||||||
val code = param.args[0] as Int
|
val code = param.args[0] as Int
|
||||||
if (code != HookStatusKeys.TRANSACTION_STATUS &&
|
if (code != HookStatusKeys.TRANSACTION_STATUS &&
|
||||||
code != HookStatusKeys.TRANSACTION_UPDATE_PRIVILEGE_SETTINGS &&
|
code != HookStatusKeys.TRANSACTION_UPDATE_PRIVILEGE_SETTINGS &&
|
||||||
code != HookStatusKeys.TRANSACTION_GET_ERRORS) {
|
code != HookStatusKeys.TRANSACTION_GET_ERRORS &&
|
||||||
|
code != HookStatusKeys.TRANSACTION_GET_INSTALLED_PACKAGES) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val data = param.args[1] as Parcel
|
val data = param.args[1] as Parcel
|
||||||
@@ -72,6 +74,15 @@ class HookIConnectivityManagerOnTransact(
|
|||||||
param.result = true
|
param.result = true
|
||||||
return
|
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 enabled = data.readInt() != 0
|
||||||
val slice = ParceledListSlice.CREATOR.createFromParcel(data, PackageEntry::class.java.classLoader)
|
val slice = ParceledListSlice.CREATOR.createFromParcel(data, PackageEntry::class.java.classLoader)
|
||||||
val packages = HashSet<String>()
|
val packages = HashSet<String>()
|
||||||
@@ -117,4 +128,73 @@ class HookIConnectivityManagerOnTransact(
|
|||||||
false
|
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> {
|
suspend fun getInstalledPackages(flags: Int, retryFlags: Int): List<PackageInfo> {
|
||||||
return when (val s = strategy) {
|
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.UserSelected -> RootClient.getInstalledPackages(flags)
|
||||||
is PackageQueryStrategy.Direct -> getPackagesViaPackageManager(flags, retryFlags)
|
is PackageQueryStrategy.Direct -> getPackagesViaPackageManager(flags, retryFlags)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,11 @@ object PackageQueryManager {
|
|||||||
|
|
||||||
suspend fun getInstalledPackages(flags: Int, retryFlags: Int): List<PackageInfo> {
|
suspend fun getInstalledPackages(flags: Int, retryFlags: Int): List<PackageInfo> {
|
||||||
return when (val s = strategy) {
|
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) {
|
is PackageQueryStrategy.UserSelected -> when (s.mode) {
|
||||||
Settings.PACKAGE_QUERY_MODE_ROOT -> RootClient.getInstalledPackages(flags)
|
Settings.PACKAGE_QUERY_MODE_ROOT -> RootClient.getInstalledPackages(flags)
|
||||||
else -> ShizukuPackageManager.getInstalledPackages(flags)
|
else -> ShizukuPackageManager.getInstalledPackages(flags)
|
||||||
|
|||||||
Reference in New Issue
Block a user