From 38c839eb924d19818010f851a2d013fe56ab23dc Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sat, 25 Jul 2026 23:29:18 +0200 Subject: [PATCH 1/2] Register the `serial` proxy service on Android 10 and below The Zygisk module polls `ServiceManager.getService("serial")` while specializing `system_server`, but since #648 the matching `ServiceManager.addService` call sits inside the `SDK_INT >= R` branch that exists only for `IServiceManager.registerForNotifications`. On Android 8.1 to 10 nothing claims the name, so the module aborts the injection after ten attempts. Keep only `registerForNotifications` behind the version check. Without the callback we cannot capture the real service, but its own registration replaces our proxy in servicemanager anyway. --- .../vector/daemon/ipc/SystemServerService.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/SystemServerService.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/SystemServerService.kt index dc229ad22..e53a5d4b4 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/SystemServerService.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/SystemServerService.kt @@ -24,6 +24,8 @@ object SystemServerService : ILSPSystemServerService.Stub(), IBinder.DeathRecipi // Register as the service name early to setup an IPC for `system_server`. Log.d(TAG, "Registering bridge service for `system_server` with name `$serviceName`.") + // `IServiceManager.registerForNotifications` is only available since Android R. + // On older platforms we simply let the real service replace our proxy in servicemanager. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val callback = object : IServiceCallback.Stub() { @@ -39,13 +41,17 @@ object SystemServerService : ILSPSystemServerService.Stub(), IBinder.DeathRecipi override fun asBinder(): IBinder = this } - runCatching { - getSystemServiceManager().registerForNotifications(serviceName, callback) - ServiceManager.addService(serviceName, this) - proxyServiceName = serviceName - } + runCatching { getSystemServiceManager().registerForNotifications(serviceName, callback) } .onFailure { Log.e(TAG, "Failed to register IServiceCallback", it) } } + + // The Zygisk module polls this name during `system_server` specialization, + // so it must be claimed on every supported platform. + runCatching { + ServiceManager.addService(serviceName, this) + proxyServiceName = serviceName + } + .onFailure { Log.e(TAG, "Failed to register proxy service `$serviceName`", it) } } override fun requestApplicationService( From 12df6c15445274302836b0163afa3923b1a47239 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Mon, 27 Jul 2026 13:55:54 +0200 Subject: [PATCH 2/2] Open the manager with the pre-R `startActivityAsUser` signature `IActivityManager.startActivityAsUserWithFeature` only arrived in Android R, but both call sites used it unconditionally, so on Android 10 the manager opened once and then took the daemon down, as reported in #773: java.lang.NoSuchMethodError: No interface method startActivityAsUserWithFeature(...) at org.matrix.vector.daemon.ipc.ManagerService.startActivityAsUserWithFeature(ManagerService.kt:379) at org.lsposed.lspd.ILSPManagerService$Stub.onTransact(ILSPManagerService.java:377) E/VectorDaemon: Uncaught exception in Daemon The error escaped the binder transaction because that call site was not guarded, and the manager was then left in the shell host process without a daemon, which is where the `BugreportWarningActivity` crash comes from. Route both call sites through `startActivityAsUserCompat`, following the `registerReceiverCompat` pattern: pick the signature the platform provides and turn a failure into a negative result instead of an uncaught error. --- .../vector/daemon/ipc/ManagerService.kt | 33 +++---------------- .../vector/daemon/system/SystemExtensions.kt | 28 ++++++++++++++++ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/ManagerService.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/ManagerService.kt index 8c4f1c40b..7cc849980 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/ManagerService.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/ManagerService.kt @@ -158,22 +158,9 @@ object ManagerService : ILSPManagerService.Stub() { fun openManager(withData: Uri?) { val intent = getManagerIntent() ?: return val launchIntent = Intent(intent).apply { data = withData } - runCatching { - activityManager?.startActivityAsUserWithFeature( - SystemContext.appThread, - "android", - null, - launchIntent, - launchIntent.type, - null, - null, - 0, - 0, - null, - null, - 0) - } - .onFailure { Log.e(TAG, "Failed to open manager", it) } + // Negative results are `ActivityManager.START_*` errors, the positive ones are all successes. + val result = activityManager?.startActivityAsUserCompat(launchIntent, 0) ?: -1 + if (result < 0) Log.e(TAG, "Failed to open manager: $result") } /** Fixes permissions for the WebView cache. */ @@ -376,19 +363,7 @@ object ManagerService : ILSPManagerService.Stub() { wm?.lockNow(null) } } - return activityManager?.startActivityAsUserWithFeature( - SystemContext.appThread, - "android", - null, - intent, - intent.type, - null, - null, - 0, - 0, - null, - null, - userId) ?: -1 + return activityManager?.startActivityAsUserCompat(intent, userId) ?: -1 } override fun queryIntentActivitiesAsUser( diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/system/SystemExtensions.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/system/SystemExtensions.kt index 6aa5997ec..8f62fccf8 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/system/SystemExtensions.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/system/SystemExtensions.kt @@ -309,6 +309,34 @@ fun IActivityManager.broadcastIntentCompat(intent: Intent) { .onFailure { Log.e(TAG, "broadcastIntent failed", it) } } +// `startActivityAsUserWithFeature` only arrived in Android R: on older platforms the same call +// exists without the calling feature id, and using the newer one throws `NoSuchMethodError`. +fun IActivityManager.startActivityAsUserCompat(intent: Intent, userId: Int): Int { + val appThread = SystemContext.appThread + return runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + startActivityAsUserWithFeature( + appThread, + "android", + null, + intent, + intent.type, + null, + null, + 0, + 0, + null, + null, + userId) + } else { + startActivityAsUser( + appThread, "android", intent, intent.type, null, null, 0, 0, null, null, userId) + } + } + .onFailure { Log.e(TAG, "startActivityAsUser failed", it) } + .getOrDefault(-1) +} + fun IUserManager.getUserName(userId: Int): String { return runCatching { getUserInfo(userId)?.name }.getOrNull() ?: userId.toString() }