Android runtime internals
How the Extentos Android SDK is a thin Kotlin shell over a shared Rust core (uniffi + jniLibs), the four transports and how TransportChoice.Auto resolves, and the Meta DAT bridge.
You don't need this page to build an app — the capability surface is the contract. It's here for when you want to understand what's running underneath, debug a transport selection, or reason about cross-platform parity.
Kotlin shell over a shared Rust core
The Android SDK is deliberately thin. Vendor-agnostic logic — state machines, data shapes, event vocabularies, protocol handling, display strings — lives once in a shared Rust core (extentos-core), not in the Kotlin code. The Kotlin layer holds only what must be platform-bound: Meta DAT calls, Bluetooth SCO/HFP audio, Compose UI, and the Android Context plumbing.
The core reaches Kotlin through uniffi:
extentos_core.kt— uniffi-generated Kotlin bindings for the core's types and functions. This file is codegen output — never hand-edit it; the source of truth is the Rust crate, and the bindings are regenerated from it.jniLibs/*.so— the compiled Rust core for each Android ABI, loaded at runtime. The bindings load the native library through JNA.
A bug fixed in the Rust core lands on Android and iOS at once; that's the whole point of keeping the shells thin. The same core compiles to an xcframework for the iOS SDK. (See Architecture for the cross-platform picture.)
Four transports behind one abstraction
Everything above the transport — every capability client — talks to a single GlassesTransport interface. Four implementations swap in behind it:
| Transport | What it is | Network |
|---|---|---|
SystemAudioTransport | The vendorless baseline: mic + speaker over the OS's own Bluetooth routing. No vendor SDK, so no camera and no display. | Bluetooth HFP (via the OS) |
RealMetaTransport | Production glasses via Meta DAT (com.meta.wearable, 0.8.0, through com.extentos:glasses-meta). | Bluetooth / DAT |
BrowserSimTransport | WebSocket to the Extentos backend; the hardware surrogate runs in a browser tab. | WebSocket |
LocalSimTransport | In-process mock (no network), for a fast inner loop and JVM unit tests. | None |
Because the capability clients only see GlassesTransport, the simulator runs the same library code as production — only the transport and I/O substrate differ. A failure in the simulator is a real failure. (See Transport vs. app behavior.)
How TransportChoice.Auto resolves
Auto (the default) picks a transport at create() time by walking these rules in order, stopping at the first match:
- Baked session URL — a
BuildConfig.EXTENTOS_SESSION_URLinjected at compile time →BrowserSim. EXTENTOS_SESSION_URLenv var — a runtime dev override (handy for emulator runs) →BrowserSim.- A registered vendor claims — vendor modules (e.g.
com.extentos:glasses-meta) register themselves at app start and are asked, in order, whether their hardware is present. Meta claims when a Meta/Ray-Ban/Oakley device is bonded over Bluetooth →RealMeta. (That check needsBLUETOOTH_CONNECT; without the grant it returns false and falls through, then self-heals on the next launch once granted.) With no vendor module on the classpath the registry is empty and resolution continues unchanged. - Dev pairing —
debug = truewith aContextbut no URL and no vendor claim →BrowserSimin pairing mode (the connection page shows a short code to type into the simulator). Debug-only: a release build never reaches this rung. - System audio — a real device with a
Contextand no vendor claim →SystemAudio, the vendorless baseline. Mic and speaker over whatever the OS has routed; the full agent runtime works, camera and display do not. - Fallback — no
Contextat all (headless JVM tests, CI) →LocalSim.
The net effect with zero host config: a phone with paired glasses resolves to real hardware; an emulator resolves to the simulator; a real phone with none resolves to SystemAudio, the vendorless audio baseline. You can always force a specific transport by setting ExtentosConfig.transport explicitly (SystemAudio, RealMeta, Simulated.Local, or Simulated.Browser(url)). The resolved choice is recorded on runtime.events as a transport.selected log line.
This differs from iOS on purpose. Android can enumerate bonded Bluetooth
devices, so rung 3 has a direct signal for "are the glasses actually here?" and
rung 5 can safely catch everything else. iOS has no equivalent API, so its
.auto chain keys the audio-baseline rung off the app's declared capability
footprint instead. The practical difference: an app that declares camera, on a
phone with no glasses paired, lands on SystemAudio here and on RealMeta on
iOS (where connect() then reports no eligible device). See
iOS runtime internals.
The Meta DAT bridge
RealMetaTransport is backed by MetaHardwareBridge, the Kotlin glue to Meta's Device Access Toolkit. It owns the platform-bound work the core can't do:
- DAT session init / registration (needs a
Contextand a foregroundActivity— supplied viaExtentosConfig.applicationContextandactivityProvider). - Bluetooth SCO/HFP routing for the glasses mic and speaker (wideband mSBC, 16 kHz duplex), which is why the library declares
MODIFY_AUDIO_SETTINGS. - Surfacing hardware events (lifecycle, audio route, call state) up into the core's event vocabulary.
The Meta DAT SDK arrives ONLY through com.extentos:glasses-meta, and only if you add that dependency. com.extentos:glasses carries no vendor SDK, so an app that never uses camera or display ships none of it and needs no credentialed repository. (This differs from iOS, where the DAT modules are still unconditional dependencies of GlassesCore — the module split is Android-only today.) You never declare com.meta.wearable yourself either way; it comes in transitively through glasses-meta.
Generated bindings (
extentos_core.kt, theuniffi/output,jniLibs/) are build artifacts. If something looks wrong in them, the fix is in the Rust source, regenerated — not an edit to the generated file.
Related
Architecture
How Extentos fits together — AI agent, MCP server, native Kotlin/Swift SDK, four transports (system audio, Meta DAT, browser sim, local in-memory sim), and the backend.
Transport vs app simulation
Meta's Mock Device Kit simulates the transport layer; Extentos simulates the app layer — voice, photo capture, and the wearing experience. Both matter.
Initialize the Android SDK
Create ExtentosGlasses with ExtentosGlasses.create, configure ExtentosConfig (environment, applicationContext, transport), reach the sub-clients, and pattern-match ExtentosResult.
Android SDK threading model
How the Extentos Android SDK uses Kotlin coroutines and Flow — suspend functions for one-shot ops, Flow/StateFlow for streams, and which dispatcher to collect on.
Android SDK threading model
How the Extentos Android SDK uses Kotlin coroutines and Flow — suspend functions for one-shot ops, Flow/StateFlow for streams, and which dispatcher to collect on.
Vendors
Smart-glasses vendors Extentos supports. Meta smart glasses are the production target as of 2026-07; Android XR is in preview (transport built and emulator-proven, module unpublished, no shipping hardware); Brilliant Labs is in preview (transport built and published, but no hardware has run it and there is no emulator); Apple is a tracked roadmap vendor.