From eb918c8c749952453bbd292c16c9ba13655cbab0 Mon Sep 17 00:00:00 2001 From: Davide Garberi Date: Sun, 18 Jan 2026 12:17:07 +0100 Subject: [PATCH 1/5] client: Switch from using activity to context --- .../client/HolochainServiceAdminClient.kt | 9 ++++----- .../client/HolochainServiceAppClient.kt | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAdminClient.kt b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAdminClient.kt index 4f1322d4..2f2d1bb0 100644 --- a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAdminClient.kt +++ b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAdminClient.kt @@ -1,6 +1,5 @@ package org.holochain.androidserviceruntime.client -import android.app.Activity import android.content.ComponentName import android.content.Context import android.content.Intent @@ -10,7 +9,7 @@ import android.util.Log import kotlinx.coroutines.delay class HolochainServiceAdminClient( - private val activity: Activity, + private val context: Context, private val serviceComponentName: ComponentName, ) { private var mService: IHolochainServiceAdmin? = null @@ -43,7 +42,7 @@ class HolochainServiceAdminClient( intent.component = this.serviceComponentName intent.putExtra("config", RuntimeNetworkConfigFfiParcel(config)) - this.activity.startForegroundService(intent) + this.context.startForegroundService(intent) } /** @@ -56,12 +55,12 @@ class HolochainServiceAdminClient( // Giving the intent a unique action ensures the HolochainService `onBind()` callback is // triggered. - val packageName: String = this.activity.getPackageName() + val packageName: String = this.context.packageName val intent = Intent(packageName) intent.putExtra("api", "admin") intent.setComponent(this.serviceComponentName) - this.activity.bindService(intent, this.mConnection, Context.BIND_ABOVE_CLIENT) + this.context.bindService(intent, this.mConnection, Context.BIND_ABOVE_CLIENT) } /** diff --git a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt index 7bf39d4c..6f5812c6 100644 --- a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt +++ b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt @@ -1,6 +1,5 @@ package org.holochain.androidserviceruntime.client -import android.app.Activity import android.content.ComponentName import android.content.Context import android.content.Intent @@ -10,7 +9,7 @@ import android.util.Log import kotlinx.coroutines.delay class HolochainServiceAppClient( - private val activity: Activity, + private val context: Context, private val serviceComponentName: ComponentName, ) { private var mService: IHolochainServiceApp? = null @@ -43,13 +42,13 @@ class HolochainServiceAppClient( // Giving the intent a unique action ensures the HolochainService `onBind()` callback is // triggered. - val packageName: String = this.activity.getPackageName() + val packageName: String = this.context.packageName val intent = Intent("$packageName:$installedAppId") intent.putExtra("api", "app") intent.putExtra("installedAppId", installedAppId) intent.setComponent(this.serviceComponentName) - this.activity.bindService(intent, this.mConnection, Context.BIND_ABOVE_CLIENT) + this.context.bindService(intent, this.mConnection, Context.BIND_ABOVE_CLIENT) } /** From aec3eb97dfbbcee27415fb4475058196653c0f3a Mon Sep 17 00:00:00 2001 From: Davide Garberi Date: Sun, 18 Jan 2026 17:48:51 +0100 Subject: [PATCH 2/5] libraries: client: Allow specifying a onDisconnected callback for the app service --- .../androidserviceruntime/client/HolochainServiceAppClient.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt index 6f5812c6..6fb3e257 100644 --- a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt +++ b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.delay class HolochainServiceAppClient( private val context: Context, private val serviceComponentName: ComponentName, + private val onDisconnected: (() -> Unit)? = null, ) { private var mService: IHolochainServiceApp? = null private val logTag = "HolochainServiceAppClient" @@ -29,6 +30,7 @@ class HolochainServiceAppClient( override fun onServiceDisconnected(className: ComponentName) { mService = null Log.d(logTag, "IHolochainServiceApp disconnected") + onDisconnected?.invoke() } } From 5847ea039f2cce51680de01b0e4498e6be43a38b Mon Sep 17 00:00:00 2001 From: Davide Garberi Date: Sun, 18 Jan 2026 18:46:42 +0100 Subject: [PATCH 3/5] libraries: client: Allow specifying a onConnected callback for the app service --- .../androidserviceruntime/client/HolochainServiceAppClient.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt index 6fb3e257..8edc226e 100644 --- a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt +++ b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.delay class HolochainServiceAppClient( private val context: Context, private val serviceComponentName: ComponentName, + private val onConnected: (() -> Unit)? = null, private val onDisconnected: (() -> Unit)? = null, ) { private var mService: IHolochainServiceApp? = null @@ -25,6 +26,7 @@ class HolochainServiceAppClient( ) { mService = IHolochainServiceApp.Stub.asInterface(service) Log.d(logTag, "IHolochainServiceApp connected") + onConnected?.invoke() } override fun onServiceDisconnected(className: ComponentName) { From 6304229a5e70100828bb87a10f14a46408038d63 Mon Sep 17 00:00:00 2001 From: Davide Garberi Date: Mon, 19 Jan 2026 17:35:13 +0100 Subject: [PATCH 4/5] libraries: Check if the service is ready before executing setupApp --- .../client/IHolochainServiceApp.aidl | 1 + .../client/HolochainServiceAppClient.kt | 33 +++++++++++++++++++ .../service/HolochainService.kt | 7 ++++ 3 files changed, 41 insertions(+) diff --git a/libraries/client/src/main/aidl/org/holochain/androidserviceruntime/client/IHolochainServiceApp.aidl b/libraries/client/src/main/aidl/org/holochain/androidserviceruntime/client/IHolochainServiceApp.aidl index 0f2ba986..9e9663f9 100644 --- a/libraries/client/src/main/aidl/org/holochain/androidserviceruntime/client/IHolochainServiceApp.aidl +++ b/libraries/client/src/main/aidl/org/holochain/androidserviceruntime/client/IHolochainServiceApp.aidl @@ -5,6 +5,7 @@ import org.holochain.androidserviceruntime.client.InstallAppPayloadFfiParcel; import org.holochain.androidserviceruntime.client.ZomeCallParamsFfiParcel; interface IHolochainServiceApp { + boolean isReady(); void setupApp(IHolochainServiceCallback callback, in InstallAppPayloadFfiParcel request, boolean enableAfterInstall); void enableApp(IHolochainServiceCallback callback); void ensureAppWebsocket(IHolochainServiceCallback callback); diff --git a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt index 8edc226e..fc49416e 100644 --- a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt +++ b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt @@ -106,6 +106,7 @@ class HolochainServiceAppClient( ): AppAuthFfi { this.connect(installAppPayload.installedAppId!!) this.waitForConnectReady() + this.waitForServiceReady() return this.setupApp(installAppPayload, enableAfterInstall) } @@ -149,6 +150,13 @@ class HolochainServiceAppClient( return callbackDeferred.await() } + /** + * Checks if the Holochain runtime is ready to receive calls. + * + * @return true if connected and runtime is ready, false otherwise + */ + fun isReady(): Boolean = this.mService?.isReady() ?: false + /** * Polls until connected to the service, or the timeout has elapsed. * @@ -168,4 +176,29 @@ class HolochainServiceAppClient( elapsedMs += intervalMs } } + + /** + * Polls until the Holochain service runtime is ready, or the timeout has elapsed. + * + * This is necessary because the service may be connected (onBind returned) but the + * Holochain conductor may not have finished starting yet. + * + * @param timeoutMs Maximum time to wait for service to be ready in milliseconds (default: 30000ms) + * @param intervalMs Time between readiness checks in milliseconds (default: 100ms) + * @return true if service became ready within timeout, false otherwise + */ + suspend fun waitForServiceReady( + timeoutMs: Long = 30000L, + intervalMs: Long = 100L, + ): Boolean { + var elapsedMs = 0L + while (elapsedMs <= timeoutMs) { + Log.d(logTag, "waitForServiceReady " + elapsedMs) + if (this.isReady()) return true + + delay(intervalMs) + elapsedMs += intervalMs + } + return false + } } diff --git a/libraries/service/src/main/java/org/holochain/androidserviceruntime/service/HolochainService.kt b/libraries/service/src/main/java/org/holochain/androidserviceruntime/service/HolochainService.kt index 9a63747a..ac4d3970 100644 --- a/libraries/service/src/main/java/org/holochain/androidserviceruntime/service/HolochainService.kt +++ b/libraries/service/src/main/java/org/holochain/androidserviceruntime/service/HolochainService.kt @@ -584,6 +584,13 @@ class HolochainService : Service() { private var authorized = false private var clientPackageName: String? = null + // Is the conductor started and ready to receive calls + // No authorization needed + override fun isReady(): Boolean { + Log.d(logTag, "isReady") + return runtime != null + } + // / Setup an app override fun setupApp( callback: IHolochainServiceCallback, From ffd8d11f00c792bfaedf823cb892eac532befc8e Mon Sep 17 00:00:00 2001 From: Davide Garberi Date: Tue, 20 Jan 2026 15:38:58 +0100 Subject: [PATCH 5/5] client: HolochainServiceAppClient: Throw exception if connection not established --- .../client/HolochainServiceAppClient.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt index fc49416e..1e191c79 100644 --- a/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt +++ b/libraries/client/src/main/java/org/holochain/androidserviceruntime/client/HolochainServiceAppClient.kt @@ -99,13 +99,18 @@ class HolochainServiceAppClient( * @param installAppPayload The payload containing app installation data * @param enableAfterInstall Whether to enable the app after installation * @return AppAuthFfi object containing authentication and connection information + * @throws HolochainServiceNotConnectedException if not connected to the service */ suspend fun connectSetupApp( installAppPayload: InstallAppPayloadFfi, enableAfterInstall: Boolean, ): AppAuthFfi { this.connect(installAppPayload.installedAppId!!) - this.waitForConnectReady() + + if (!this.waitForConnectReady()) { + throw HolochainServiceNotConnectedException() + } + this.waitForServiceReady() return this.setupApp(installAppPayload, enableAfterInstall) } @@ -166,15 +171,16 @@ class HolochainServiceAppClient( private suspend fun waitForConnectReady( timeoutMs: Long = 100L, intervalMs: Long = 5L, - ) { + ): Boolean { var elapsedMs = 0L while (elapsedMs <= timeoutMs) { Log.d(logTag, "waitForConnectReady " + elapsedMs) - if (this.mService != null) break + if (this.mService != null) return true delay(intervalMs) elapsedMs += intervalMs } + return false } /**