You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
RemoteViews setters must be assembled in-process from Java.
Activity-with-bridge
android.app.Activity
Already handled by existingGossamerActivity.
The estate-wide language policy bans Kotlin and (modulo a tight android-shim carve-out) Java. The cleanest compliance is minimal Java shim base classes in this repo that delegate to native (Zig/Rust) via JNI, so consumers extend them with ~5-20 lines of subclass code per surface.
This issue tracks the design + landing of a gossamer-android-services companion module here in hyperpolymath/gossamer.
Motivating consumer
hyperpolymath/neurophone — RFC hyperpolymath/neurophone#97 is migrating from Kotlin/Android to Gossamer. Currently 7 Kotlin files + 3 *.gradle.kts (~926 LoC) implement: foreground service with sensor capture, boot-restart receiver, three home-screen widget classes, intent handling. The migration is blocked on this companion landing per owner directive 2026-06-02 (option Q6: "contribute upstream NOW").
Future consumers (any estate Android app, idaptik-ums Android port, etc.) inherit the same base classes for free.
RemoteViews setters must run in Java (cannot be assembled cleanly from JNI), so the abstraction is: Java fetches widget state from native, then renders RemoteViews based on it.
Open design question: handle ownership. The current webview_android.zig uses GossamerHandle from main.zig. For services we likely want a separate ServiceHandle type with its own JNI state, so a service running in the background doesn't share state with the foreground Activity's webview. Recommend independent handles + an explicit IPC primitive for the rare case where the service needs to talk to the foreground UI (e.g. neurophone's "publish neural-context to widget" tick).
Default implementations / payload conventions
Consumers should NOT have to invent payload formats. The companion provides conventions:
nativeFetchWidgetState returns JSON. Top-level object with whatever the widget needs; rendering is the subclass's job (because RemoteViews keys vary by layout).
getActionPrefix() is mandatory — namespaces widget broadcast actions to avoid collisions in multi-widget apps.
What this PR does NOT do
Does NOT replace Gossamer's existing webview/Activity layer. The four shim classes are additive.
Does NOT introduce any consumer-specific code (no neurophone_* anywhere). Consumer-specific logic lives in the consumer repo's subclass.
Does NOT change the GossamerBridge IPC mechanism. Service-side IPC (if any) is a separate design question, not part of this companion.
Test plan
Each base class has an emulator-level test that builds the class on its own (without a consumer), verifies it can be instantiated as an Android Service/BroadcastReceiver/AppWidgetProvider, and that the native* JNI exports exist (avoid UnsatisfiedLinkError).
At least one synthetic consumer in tests/services/ exercises each subclass override path: getChannelId, getServiceClass, getWidgetLayout, renderWidget. A tiny tests/services/no-op-app/ Android app subclasses each.
Integration: neurophone's NeurophoneRuntimeService subclass compiles + boots a foreground service in the emulator. Validates the API shape against the real consumer.
Negative: a subclass that fails to call super.onCreate() produces a clear error (use final on lifecycle methods to make this impossible at compile time).
Sequencing — what unblocks what
This companion is blocking for hyperpolymath/neurophone#97 sub-PRs #3-#9 per Q6 of the RFC. Recommended sequence:
This issue discussion + design approval.
Companion PR lands here in Gossamer (this companion module).
hyperpolymath/neurophone/docs/migrations/RFC-ANDROID-KOTLIN-TO-RUST.adoc — full migration plan.
hyperpolymath/neurophone/docs/migrations/JNI-SURFACE-AUDIT.adoc — JNI surface audit for the Rust-side core (separate from this Java shim layer; the two meet at the JNI boundary).
hyperpolymath/.git-private-farm#66 — propagation primitive (unrelated; same session).
Owner sign-off ask
API surface OK? Specifically: the four base classes, the final on lifecycle methods (forces subclasses to override hooks rather than rewriting lifecycle), the JSON conventions for config/state.
Module layout OK?io.gossamer.services.* subpackage; services_android.zig sibling to webview_android.zig.
Handle ownership — independent ServiceHandle vs sharing GossamerHandle? Recommend independent.
Subscribed-sensors API — currently int[] getSubscribedSensors(). Alternative: a more typed builder. Keeping it primitive matches the rest of the JNI surface.
Why
Android instantiates the following classes by name from JVM bytecode at platform boundaries — Rust/Zig cannot satisfy these contracts directly:
android.app.ServiceonCreate / onStartCommand / onDestroy.android.content.BroadcastReceiver<receiver android:name=".X">in manifest.android.appwidget.AppWidgetProvider(extends BroadcastReceiver)RemoteViewssetters must be assembled in-process from Java.android.app.ActivityGossamerActivity.The estate-wide language policy bans Kotlin and (modulo a tight android-shim carve-out) Java. The cleanest compliance is minimal Java shim base classes in this repo that delegate to native (Zig/Rust) via JNI, so consumers extend them with ~5-20 lines of subclass code per surface.
This issue tracks the design + landing of a
gossamer-android-servicescompanion module here inhyperpolymath/gossamer.Motivating consumer
hyperpolymath/neurophone— RFChyperpolymath/neurophone#97is migrating from Kotlin/Android to Gossamer. Currently 7 Kotlin files + 3*.gradle.kts(~926 LoC) implement: foreground service with sensor capture, boot-restart receiver, three home-screen widget classes, intent handling. The migration is blocked on this companion landing per owner directive 2026-06-02 (option Q6: "contribute upstream NOW").Future consumers (any estate Android app, idaptik-ums Android port, etc.) inherit the same base classes for free.
Proposed module layout
API sketch — four base classes
1.
GossamerForegroundService extends android.app.ServiceNeurophone subclass — ~12 lines:
2.
GossamerBootReceiver extends BroadcastReceiverNeurophone subclass — 3 lines:
3.
GossamerAppWidgetProvider extends AppWidgetProviderRemoteViewssetters must run in Java (cannot be assembled cleanly from JNI), so the abstraction is: Java fetches widget state from native, then renders RemoteViews based on it.Neurophone subclass — ~20 lines:
4. Existing
GossamerActivity— minor extensionAdd a single hook:
Backwards-compatible — existing subclasses unaffected unless they override the new hook.
Native (Zig) surface
gossamer/ffi/services_android.zigmirrors the existingwebview_android.zigpattern (JNI types, function table indirection, opaque handle, error type).Approximate exports:
Open design question: handle ownership. The current
webview_android.zigusesGossamerHandlefrommain.zig. For services we likely want a separateServiceHandletype with its own JNI state, so a service running in the background doesn't share state with the foreground Activity's webview. Recommend independent handles + an explicit IPC primitive for the rare case where the service needs to talk to the foreground UI (e.g. neurophone's "publish neural-context to widget" tick).Default implementations / payload conventions
Consumers should NOT have to invent payload formats. The companion provides conventions:
getNativeConfig()returns JSON. Conventional schema documented inservices/README.adoc. Default empty{}.nativeFetchWidgetStatereturns JSON. Top-level object with whatever the widget needs; rendering is the subclass's job (because RemoteViews keys vary by layout).getActionPrefix()is mandatory — namespaces widget broadcast actions to avoid collisions in multi-widget apps.What this PR does NOT do
neurophone_*anywhere). Consumer-specific logic lives in the consumer repo's subclass.GossamerBridgeIPC mechanism. Service-side IPC (if any) is a separate design question, not part of this companion.Test plan
Service/BroadcastReceiver/AppWidgetProvider, and that thenative*JNI exports exist (avoidUnsatisfiedLinkError).tests/services/exercises each subclass override path:getChannelId,getServiceClass,getWidgetLayout,renderWidget. A tinytests/services/no-op-app/Android app subclasses each.NeurophoneRuntimeServicesubclass compiles + boots a foreground service in the emulator. Validates the API shape against the real consumer.super.onCreate()produces a clear error (usefinalon lifecycle methods to make this impossible at compile time).Sequencing — what unblocks what
This companion is blocking for
hyperpolymath/neurophone#97sub-PRs #3-#9 per Q6 of the RFC. Recommended sequence:NeurophoneMainActivity extends GossamerActivity.Roadmap alignment
This implements several of the unchecked Phase 3 Android items in
ROADMAP.adoc:122-127:[ ] JNI FindClass / GetMethodID / CallVoidMethod for WebView operations— required for the widget RemoteViews fetch path too.[ ] addJavascriptInterface for IPC bridge— already done by GossamerBridge; the companion uses the same pattern for the service IPC.[ ] Android NDK build integration— the companion addsservices_android.zigto the build.[ ] Gradle project scaffolding for Java launcher— the companion clarifies the Gradle layout for service-bearing apps.Related
hyperpolymath/neurophone#97— Kotlin → Gossamer migration RFC (the consumer driving this).hyperpolymath/neurophone/docs/migrations/RFC-ANDROID-KOTLIN-TO-RUST.adoc— full migration plan.hyperpolymath/neurophone/docs/migrations/JNI-SURFACE-AUDIT.adoc— JNI surface audit for the Rust-side core (separate from this Java shim layer; the two meet at the JNI boundary).hyperpolymath/.git-private-farm#66— propagation primitive (unrelated; same session).Owner sign-off ask
finalon lifecycle methods (forces subclasses to override hooks rather than rewriting lifecycle), the JSON conventions for config/state.io.gossamer.services.*subpackage;services_android.zigsibling towebview_android.zig.ServiceHandlevs sharingGossamerHandle? Recommend independent.int[] getSubscribedSensors(). Alternative: a more typed builder. Keeping it primitive matches the rest of the JNI surface.🤖 Generated with Claude Code