diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 57dab95..be50ad6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -24,14 +24,6 @@ android {
buildConfig = true
viewBinding = true
}
-
- applicationVariants.all {
- kotlin {
- sourceSets.getByName(name) {
- kotlin.srcDir("build/generated/ksp/$name/kotlin")
- }
- }
- }
}
kotlin {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ece4b4d..fbe9e9d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -43,6 +43,12 @@
+
+
diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt b/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt
index 34350e6..a5309d0 100644
--- a/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt
+++ b/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt
@@ -1,5 +1,6 @@
package icu.nullptr.hidemyapplist.service
+import android.os.IBinder
import android.os.IBinder.DeathRecipient
import android.os.Parcel
import android.os.RemoteException
@@ -13,7 +14,7 @@ import java.lang.reflect.Proxy
object ServiceClient : IHMAService, DeathRecipient {
- private const val TAG = "ServiceHelper"
+ private const val TAG = "ServiceClient"
private class ServiceProxy(private val obj: IHMAService) : InvocationHandler {
override fun invoke(proxy: Any?, method: Method, args: Array?): Any? {
@@ -27,14 +28,16 @@ object ServiceClient : IHMAService, DeathRecipient {
@Volatile
private var service: IHMAService? = null
- override fun binderDied() {
- service = null
- Log.e(TAG, "Binder died")
+ fun linkService(binder: IBinder) {
+ service = Proxy.newProxyInstance(
+ javaClass.classLoader,
+ arrayOf(IHMAService::class.java),
+ ServiceProxy(IHMAService.Stub.asInterface(binder))
+ ) as IHMAService
+ binder.linkToDeath(this, 0)
}
- override fun asBinder() = service?.asBinder()
-
- private fun getService(): IHMAService? {
+ private fun getServiceLegacy(): IHMAService? {
if (service != null) return service
val pm = ServiceManager.getService("package")
val data = Parcel.obtain()
@@ -65,21 +68,28 @@ object ServiceClient : IHMAService, DeathRecipient {
return service
}
- override fun getServiceVersion() = getService()?.serviceVersion ?: 0
+ override fun binderDied() {
+ service = null
+ Log.e(TAG, "Binder died")
+ }
- override fun getFilterCount() = getService()?.filterCount ?: 0
+ override fun asBinder() = service?.asBinder()
- override fun getLogs() = getService()?.logs
+ override fun getServiceVersion() = getServiceLegacy()?.serviceVersion ?: 0
+
+ override fun getFilterCount() = getServiceLegacy()?.filterCount ?: 0
+
+ override fun getLogs() = getServiceLegacy()?.logs
override fun clearLogs() {
- getService()?.clearLogs()
+ getServiceLegacy()?.clearLogs()
}
override fun syncConfig(json: String) {
- getService()?.syncConfig(json)
+ getServiceLegacy()?.syncConfig(json)
}
override fun stopService(cleanEnv: Boolean) {
- getService()?.stopService(cleanEnv)
+ getServiceLegacy()?.stopService(cleanEnv)
}
}
diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceProvider.kt b/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceProvider.kt
new file mode 100644
index 0000000..b01e618
--- /dev/null
+++ b/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceProvider.kt
@@ -0,0 +1,28 @@
+package icu.nullptr.hidemyapplist.service
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.net.Uri
+import android.os.Bundle
+
+class ServiceProvider : ContentProvider() {
+
+ override fun onCreate() = false
+
+ override fun query(p0: Uri, p1: Array?, p2: String?, p3: Array?, p4: String?) = null
+
+ override fun getType(p0: Uri) = null
+
+ override fun insert(p0: Uri, p1: ContentValues?) = null
+
+ override fun delete(p0: Uri, p1: String?, p2: Array?) = 0
+
+ override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array?) = 0
+
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
+ if (callingPackage != "android" || extras == null) return null
+ val binder = extras.getBinder("binder") ?: return null
+ ServiceClient.linkService(binder)
+ return Bundle()
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index c5a7ae8..c8282a3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -27,9 +27,9 @@ val minSdkVer by extra(24)
val targetSdkVer by extra(34)
val buildToolsVer by extra("34.0.0")
-val appVerName by extra("3.1.1")
+val appVerName by extra("3.2")
val configVerCode by extra(90)
-val serviceVerCode by extra(95)
+val serviceVerCode by extra(96)
val minBackupVerCode by extra(65)
val androidSourceCompatibility = JavaVersion.VERSION_17
diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.java b/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.java
index 4493304..d4c5979 100644
--- a/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.java
+++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.java
@@ -5,6 +5,8 @@ import java.util.Set;
public class Constants {
public static final String APP_PACKAGE_NAME = "com.tsng.hidemyapplist";
+
+ public static final String PROVIDER_AUTHORITY = APP_PACKAGE_NAME + ".ServiceProvider";
public static final String GMS_PACKAGE_NAME = "com.google.android.gms";
public static final String GSF_PACKAGE_NAME = "com.google.android.gsf";
public static final String UPDATE_URL_BASE = "https://api.nullptr.icu/android/hide-my-applist/static/";
diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/BridgeService.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/BridgeService.kt
deleted file mode 100644
index 68aae3f..0000000
--- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/BridgeService.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package icu.nullptr.hidemyapplist.xposed
-
-import android.content.pm.IPackageManager
-import android.os.Binder
-import android.os.Parcel
-import com.github.kyuubiran.ezxhelper.utils.findMethod
-import com.github.kyuubiran.ezxhelper.utils.hookBefore
-import icu.nullptr.hidemyapplist.common.BuildConfig
-import icu.nullptr.hidemyapplist.common.Constants
-
-object BridgeService {
-
- private const val TAG = "HMA-Bridge"
-
- private var appUid = 0
-
- fun register(pms: IPackageManager) {
- logI(TAG, "Initialize HMAService - Version ${BuildConfig.SERVICE_VERSION}")
- val service = HMAService(pms)
- appUid = Utils.getPackageUidCompat(service.pms, Constants.APP_PACKAGE_NAME, 0, 0)
- val appPackage = Utils.getPackageInfoCompat(service.pms, Constants.APP_PACKAGE_NAME, 0, 0)
- if (!Utils.verifyAppSignature(appPackage.applicationInfo.sourceDir)) {
- logE(TAG, "Fatal: App signature mismatch")
- return
- }
- logD(TAG, "Client uid: $appUid")
- logI(TAG, "Initialize service proxy")
- pms.javaClass.findMethod(true) {
- name == "onTransact"
- }.hookBefore { param ->
- val code = param.args[0] as Int
- val data = param.args[1] as Parcel
- val reply = param.args[2] as Parcel?
- if (myTransact(code, data, reply)) param.result = true
- }
- }
-
- private fun myTransact(code: Int, data: Parcel, reply: Parcel?): Boolean {
- if (code == Constants.TRANSACTION) {
- if (Binder.getCallingUid() == appUid) {
- logD(TAG, "Transaction from client")
- runCatching {
- data.enforceInterface(Constants.DESCRIPTOR)
- when (data.readInt()) {
- Constants.ACTION_GET_BINDER -> {
- reply?.writeNoException()
- reply?.writeStrongBinder(HMAService.instance)
- return true
- }
- else -> logW(TAG, "Unknown action")
- }
- }.onFailure {
- logE(TAG, "Transaction error", it)
- }
- } else {
- logW(TAG, "Someone else trying to get my binder?")
- }
- data.setDataPosition(0)
- reply?.setDataPosition(0)
- }
- return false
- }
-}
diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt
new file mode 100644
index 0000000..a1e4019
--- /dev/null
+++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt
@@ -0,0 +1,78 @@
+package icu.nullptr.hidemyapplist.xposed
+
+import android.app.ActivityManagerHidden
+import android.content.AttributionSource
+import android.content.pm.IPackageManager
+import android.os.Build
+import android.os.Bundle
+import android.os.ServiceManager
+import icu.nullptr.hidemyapplist.common.BuildConfig
+import icu.nullptr.hidemyapplist.common.Constants
+import rikka.hidden.compat.ActivityManagerApis
+import rikka.hidden.compat.adapter.UidObserverAdapter
+
+object UserService {
+
+ private const val TAG = "HMA-UserService"
+
+ private var appUid = 0
+
+ private val uidObserver = object : UidObserverAdapter() {
+ override fun onUidActive(uid: Int) {
+ if (uid != appUid) return
+ try {
+ val provider = ActivityManagerApis.getContentProviderExternal(Constants.PROVIDER_AUTHORITY, 0, null, null)
+ if (provider == null) {
+ logE(TAG, "Failed to get provider")
+ return
+ }
+ val extras = Bundle()
+ extras.putBinder("binder", HMAService.instance)
+ val reply = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val attr = AttributionSource.Builder(1000).setPackageName("android").build()
+ provider.call(attr, Constants.PROVIDER_AUTHORITY, "", null, extras)
+ } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+ provider.call("android", null, Constants.PROVIDER_AUTHORITY, "", null, extras)
+ } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
+ provider.call("android", Constants.PROVIDER_AUTHORITY, "", null, extras)
+ } else {
+ provider.call("android", "", null, extras)
+ }
+ if (reply == null) {
+ logE(TAG, "Failed to send binder to app")
+ return
+ }
+ logI(TAG, "Send binder to app")
+ } catch (e: Throwable) {
+ logE(TAG, "onUidActive", e)
+ }
+ }
+ }
+
+ fun register(pms: IPackageManager) {
+ logI(TAG, "Initialize HMAService - Version ${BuildConfig.SERVICE_VERSION}")
+ val service = HMAService(pms)
+ appUid = Utils.getPackageUidCompat(service.pms, Constants.APP_PACKAGE_NAME, 0, 0)
+ val appPackage = Utils.getPackageInfoCompat(service.pms, Constants.APP_PACKAGE_NAME, 0, 0)
+ if (!Utils.verifyAppSignature(appPackage.applicationInfo.sourceDir)) {
+ logE(TAG, "Fatal: App signature mismatch")
+ return
+ }
+ logD(TAG, "Client uid: $appUid")
+ logI(TAG, "Register observer")
+
+ waitSystemService("activity")
+ ActivityManagerApis.registerUidObserver(
+ uidObserver,
+ ActivityManagerHidden.UID_OBSERVER_ACTIVE,
+ ActivityManagerHidden.PROCESS_STATE_UNKNOWN,
+ null
+ )
+ }
+
+ private fun waitSystemService(name: String) {
+ while (ServiceManager.getService(name) == null) {
+ Thread.sleep(1000)
+ }
+ }
+}
diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt
index 553dfa7..82ca0fd 100644
--- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt
+++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt
@@ -8,9 +8,11 @@ import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.callbacks.XC_LoadPackage
import icu.nullptr.hidemyapplist.common.Constants
+import kotlin.concurrent.thread
private const val TAG = "HMA-XposedEntry"
+@Suppress("unused")
class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage {
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
@@ -35,11 +37,13 @@ class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage {
serviceManagerHook?.unhook()
val pms = param.args[1] as IPackageManager
logD(TAG, "Got pms: $pms")
- runCatching {
- BridgeService.register(pms)
- logI(TAG, "Bridge service injected")
- }.onFailure {
- logE(TAG, "System service crashed", it)
+ thread {
+ runCatching {
+ UserService.register(pms)
+ logI(TAG, "User service started")
+ }.onFailure {
+ logE(TAG, "System service crashed", it)
+ }
}
}
}