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/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..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 @@ -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,8 +9,10 @@ import android.util.Log import kotlinx.coroutines.delay class HolochainServiceAppClient( - private val activity: Activity, + private val context: Context, private val serviceComponentName: ComponentName, + private val onConnected: (() -> Unit)? = null, + private val onDisconnected: (() -> Unit)? = null, ) { private var mService: IHolochainServiceApp? = null private val logTag = "HolochainServiceAppClient" @@ -25,11 +26,13 @@ class HolochainServiceAppClient( ) { mService = IHolochainServiceApp.Stub.asInterface(service) Log.d(logTag, "IHolochainServiceApp connected") + onConnected?.invoke() } override fun onServiceDisconnected(className: ComponentName) { mService = null Log.d(logTag, "IHolochainServiceApp disconnected") + onDisconnected?.invoke() } } @@ -43,13 +46,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) } /** @@ -96,13 +99,19 @@ 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) } @@ -146,6 +155,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. * @@ -155,14 +171,40 @@ 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 + } + + /** + * 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,