diff --git a/daemon/proguard-rules.pro b/daemon/proguard-rules.pro index 94ebe3a0e..9105d4f96 100644 --- a/daemon/proguard-rules.pro +++ b/daemon/proguard-rules.pro @@ -47,3 +47,9 @@ -repackageclasses -allowaccessmodification + +# Android R and newer only: the `android.os.IServiceCallback` subclass has to stay in a class of +# its own, or ART resolves `IServiceCallback$Stub` while verifying registerProxyService and logs a +# NoClassDefFoundError on older platforms (issue #925). Pinning stops R8 from inlining or merging +# the holder back into SystemServerService; renaming it is still fine. +-keep,allowobfuscation class org.matrix.vector.daemon.ipc.ServiceRegistrationWatcher { *; } 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 c0db44073..4aa4f9fb5 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 @@ -13,6 +13,38 @@ import org.matrix.vector.daemon.system.getSystemServiceManager private const val TAG = "VectorSystemServer" +/** + * The Android R half of [SystemServerService.registerProxyService], in a class of its own on + * purpose. + * + * `android.os.IServiceCallback` does not exist before Android R, and ART resolves the superclass of + * every type a method mentions when it verifies that method, not when the branch mentioning it + * runs. Spelling the callback out inside [SystemServerService] therefore made the daemon's very + * first call fail to resolve `IServiceCallback$Stub` on Android 9 and log a NoClassDefFoundError, + * even though the version gate meant that code was never executed (issue #925). Behind a separate + * class the resolution is deferred to this object's initialization, which the gate skips on older + * platforms. + */ +private object ServiceRegistrationWatcher { + + fun watch(serviceName: String) { + val callback = + object : IServiceCallback.Stub() { + // The IServiceCallback will tell us when the real Android service is ready, + // allowing us to capture it and then naturally stop intercepting traffic. + override fun onRegistration(name: String, binder: IBinder?) { + if (name == serviceName && binder != null) { + SystemServerService.adoptOriginService(name, binder) + } + } + + override fun asBinder(): IBinder = this + } + runCatching { getSystemServiceManager().registerForNotifications(serviceName, callback) } + .onFailure { Log.e(TAG, "Failed to register IServiceCallback", it) } + } +} + /** * The daemon's end of the one handshake system_server gets. * @@ -36,22 +68,7 @@ object SystemServerService : Binder(), IBinder.DeathRecipient { // `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() { - // The IServiceCallback will tell us when the real Android service is ready, - // allowing us to capture it and then naturally stop intercepting traffic. - override fun onRegistration(name: String, binder: IBinder?) { - if (name == serviceName && binder != null && binder !== this@SystemServerService) { - Log.d(TAG, "Intercepted system service registration with name `$name`") - originService = binder - runCatching { binder.linkToDeath(this@SystemServerService, 0) } - } - } - - override fun asBinder(): IBinder = this - } - runCatching { getSystemServiceManager().registerForNotifications(serviceName, callback) } - .onFailure { Log.e(TAG, "Failed to register IServiceCallback", it) } + ServiceRegistrationWatcher.watch(serviceName) } // The Zygisk module polls this name during `system_server` specialization, @@ -63,6 +80,17 @@ object SystemServerService : Binder(), IBinder.DeathRecipient { .onFailure { Log.e(TAG, "Failed to register proxy service `$serviceName`", it) } } + /** + * Adopts the real system service once servicemanager reports its registration, so that + * [onTransact] starts forwarding to it. Only ever called from [ServiceRegistrationWatcher]. + */ + internal fun adoptOriginService(name: String, binder: IBinder) { + if (binder === this) return + Log.d(TAG, "Intercepted system service registration with name `$name`") + originService = binder + runCatching { binder.linkToDeath(this, 0) } + } + /** * Registers system_server and answers with its framework service, or null if this is not * system_server. Only ever called from [onTransact] below.