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) + } } } }