Choose your path
Voice apps run on any Bluetooth smart glasses with no vendor setup. Camera and display need vendor credentials. Pick the smaller path first — you can add the other later without rewriting your app.
Extentos has two integration paths, and most apps need only the first. The difference is not how much of Extentos you get — the voice runtime is identical on both. The difference is whether you need vendor credentials at all.
Which path are you on?
| You are building | Path | Vendor setup |
|---|---|---|
| A voice assistant, dictation, translation, coaching, notes — anything the user talks to and hears back | Voice | None |
| Anything that takes a photo, reads a video frame, or draws on the glasses display | Device | Required |
If you can't tell yet, start on the voice path. Adding camera later is additive — your handlers, tools and conversation logic don't change.
The voice path
Smart glasses with a microphone and speaker present themselves to the phone as a standard Bluetooth hands-free audio device. Extentos reaches them through the operating system's own audio routing, so there is no vendor SDK in the picture.
What you need:
- The Extentos SDK (
com.extentos:glasses).com.extentos:glasses-uiis optional on this path — it only provides the packaged connection page, which a voice app doesn't render. - Microphone permission (
RECORD_AUDIO/NSMicrophoneUsageDescription), requested at runtime before you start the assistant INTERNET, unless you run entirely on local models — the managed gateway is a network call- A foreground service if the assistant must keep listening while your app is backgrounded:
FOREGROUND_SERVICE+FOREGROUND_SERVICE_MICROPHONEon Android,UIBackgroundModes: [audio]on iOS - On iOS only,
usedCapabilities: [.microphone, .speaker]in your config — it picks the transport there, and leaving it empty resolves to real glasses - Your project key, if you use the assistant:
buildConfigField("String", "EXTENTOS_PROJECT_KEY", …)on Android,EXTENTOSProjectKeyin the Info.plist on iOS. Neither causes a build error when missing — the assistant just fails at runtime withAssistantError.NoApiKey - On Android,
buildFeatures { buildConfig = true }— AGP 8 defaults it off and the init snippet readsBuildConfig.DEBUG
Full sequencing for both platforms is in the voice-assistant guide.
What you do not need: a Meta Wearables Developer Center account, a Meta App ID or Client Token, a GitHub Packages token, a connection page, or a device pairing flow. None of it applies.
One open question on iOS. The Android artifact split means a voice app
genuinely doesn't pull the vendor SDK. iOS has no equivalent split yet —
Meta DAT is a dependency of GlassesCore on every iOS app, so a voice app
links it even though it never initializes it. Meta states that its SDK's use
of ExternalAccessory currently triggers App Store rejection under the MFi
and privacy-manifest rules (vendor page). We have not
verified whether merely linking DAT, without declaring the accessory
protocols or initializing it, is treated the same way. If you plan an App
Store release, treat this as unresolved and budget a review cycle. Android
and TestFlight are unaffected.
val glasses = ExtentosGlasses.create(
ExtentosConfig(applicationContext = applicationContext)
)
// `start` and `wake` are suspend functions, so they run inside a coroutine.
scope.launch {
val session = glasses.assistant.start(provider = AssistantProvider.Managed()) {
instructions = "You are a running coach. Speak briefly."
tool("log_split", "Record a lap split for the current run.") {
ToolResult.Ok("split recorded")
}
}
// Any trigger that calls session.wake() works — here, a wake phrase.
glasses.voice.onPhrase("hey coach") { session.wake() }
}You don't call connect() yourself — assistant.start(...) brings the transport up. (glasses.connection.connect() exists for apps that want to drive connection explicitly, and is what the packaged connection page calls.)
That is the integration — five short items, none of them a vendor account. You get the complete agent runtime: turn-taking, barge-in, tool calling, conversation memory, local and cloud models, and automatic routing between them.
You also get the primitives on their own, without an assistant session — including the high-quality on-device voice, which reads any text your app has in a natural voice rather than the platform's robotic one:
glasses.audio.speak(summary, SpeakConfig(voice = "kokoro-emma"))That works on this path with nothing paired at all. See on-device models.
What it runs on
Anything the phone can route voice through. The SDK pins whatever exposes a Bluetooth hands-free (HFP) route and does no device-identity checking at all, so all of these are the same code path:
| Audio route | Works | Notes |
|---|---|---|
| Smart glasses with a mic + speaker | ✅ | Including Ray-Ban Meta — their audio was never carried by the vendor SDK either |
| Ordinary Bluetooth earbuds or headsets | ✅ | AirPods, any HFP headset, a car kit |
| Nothing connected | ✅ | Falls back to the phone's own mic and speaker |
That last row matters more than it looks: your app never dead-ends because the glasses are in their case. The phone always has a microphone and a speaker, so the agent stays usable and picks the glasses back up when they reconnect.
You cannot currently tell which microphone is live. On this transport
glasses.connection.state reports Active whenever audio is available — the
phone always has a mic and a speaker — so it does not distinguish glasses
from earbuds from the phone itself. A dedicated audio-route readout is on the
roadmap; until then, don't build a "connected to your glasses" indicator on
connection.state, because it will claim glasses that aren't there.
The device path
Camera and display go through the vendor's own SDK, which means vendor credentials and a device connection flow. This is where the Meta setup lives.
Add the vendor module alongside the core:
implementation("com.extentos:glasses:2.1.2")
implementation("com.extentos:glasses-meta:2.1.2") // camera + displayThe vendor transport registers itself, so TransportChoice.Auto resolves to real glasses with no extra code. Meta needs glasses-meta because its artifacts sit behind Meta's own credentialed repo; Brilliant Labs needs nothing at all — it ships inside the SDK on both platforms, though it remains preview and unverified on hardware. Then follow Vendors: Meta Ray-Ban for the credential setup, or Android XR for projected glasses.
Moving from voice to device later
Your conversation logic does not change — handlers, tools and prompts are written against capabilities (glasses.audio.*, glasses.assistant.*), never against a vendor. What does change is the bootstrap around them, because the vendor transport needs things a voice app never needed:
- Add the vendor dependency.
- Complete the vendor's credential setup.
- Request
BLUETOOTH_CONNECTandCAMERAbeforeExtentosGlasses.create(...).TransportChoice.Autodecides the transport insidecreate(...)and keeps it; the vendor arm can only claim if it can see that glasses are bonded, and that read needsBLUETOOTH_CONNECT. - Pass an
activityProvidertoExtentosConfig— vendor registration needs a live Activity, and it defaults tonull. - Guard the new calls on the capability:
if (glasses.capabilities.camera) {
val photo = glasses.camera.capturePhoto()
}Steps 3 and 4 are the ones that surprise people; the voice-assistant guide has the worked bootstrap for a voice and camera app.
That guard is the normal shape for every capability on every path — one build ships to devices with different hardware, and absent capabilities are safe, never thrown. See capabilities.
What a device app does when there are no glasses
Once you're on the device path, your app still has to run for a user whose glasses are in a drawer. It degrades rather than breaking:
- The voice agent keeps working over whatever audio the phone has routed — earbuds, or the phone itself.
glasses.capabilities.camerareportsfalse, so guarded code simply skips the camera branch.- An unguarded
capturePhoto()returnsCaptureError.PlatformErrorwithcode = "system_audio_no_camera". It does not crash, and it does not hang. (NotNotConnected— on this transport the connection genuinely is up; there is simply no camera behind it.)
Plugging glasses in later does not move a running app onto them. Auto
resolves once, inside create(...). glasses.capabilities is re-read live, but
it is read through the chosen transport — so on the audio baseline
capabilities.camera stays false until the app is restarted with the glasses
already bonded. Design for the restart; don't poll expecting it to flip.
So a camera app and a voice app are the same app on a phone with no glasses connected — you lose exactly the capabilities the hardware was providing, and nothing else.
Glasses paired, but in their case
This is the common one, and it resolves differently from "never paired" — a Bluetooth bond is a stored pairing record that survives the glasses being powered off. So the vendor arm still claims, and transportChosen reads REAL_META even though there is nothing switched on to talk to.
Voice still works. The glasses' microphone and speaker were never reached through the vendor SDK on either platform — they are ordinary Bluetooth hands-free audio, routed by the OS. The Meta transport uses the same audio path as the vendorless baseline, so when the glasses are off the OS routes to whatever else is available: earbuds, or the phone itself. Your assistant keeps running and the user keeps talking to it.
Camera doesn't, and says so. There's no device session, so capturePhoto() returns CaptureError.NotConnected — the connection genuinely isn't Active.
The two cases stay distinguishable because capabilities reports what the transport's vendor provides, not what is switched on right now: on REAL_META with the glasses in their case, capabilities.camera is still true while connection.state is not Active. Never-paired is the opposite — capabilities.camera is false and the state is Active. So a camera app guards on both:
val canCapture = glasses.capabilities.camera &&
glasses.connection.state.value is GlassesState.ActiveWhen the user takes the glasses out of the case, the device session comes back on its own and capture starts working again — no restart, because the transport was already REAL_META. That's the difference from the never-paired case above, which does need a restart.
Already shipping? Upgrading from an older SDK
If your app was built before com.extentos:glasses-meta existed, the Meta transport used to live inside com.extentos:glasses. It doesn't any more, so a camera or display app needs one added line:
implementation("com.extentos:glasses:2.1.2")
implementation("com.extentos:glasses-ui:2.1.2")
implementation("com.extentos:glasses-meta:2.1.2") // ← add this if you use camera or displayNo Kotlin change is needed. The module registers itself at app start, so TransportChoice.Auto resolves to real glasses exactly as before.
Camera/display apps only — keep your settings.gradle.kts as it is. The Meta DAT repository block and
its read:packages token are still required — those artifacts now arrive
through glasses-meta rather than through glasses. Removing the block will
break your build.
If you skip the new dependency, nothing fails loudly. Your build succeeds and your voice features keep working, because the SDK falls back to the audio baseline — but capabilities.camera becomes false and camera calls start returning errors. The SDK logs a warning at startup naming the missing module (adb logcat -s Extentos:W), and glasses.transportChosen will read SYSTEM_AUDIO where you expect REAL_META. Check both after upgrading.
The honest boundary: the code delta is small, but the vendor's own credential setup (developer account, app registration, access token) is real work that Extentos cannot remove. Doing it later rather than up front is the saving.
Related
Quickstart with an AI agent
Install the Extentos MCP server and let your AI agent scaffold Meta Ray-Ban smart-glasses capabilities into a native iOS or Android app. Free to start.
Capabilities
The Extentos capability vocabulary — the vendor-agnostic SDK primitives (audio, camera, voice, assistant, display, hardware events) your handler subscribes to.
Meta smart glasses (Meta DAT)
Meta smart glasses developer guide: Wearables Device Access Toolkit (DAT 0.8.0) capabilities, supported models (Ray-Ban Meta, Oakley Meta, Ray-Ban Display), 2026 distribution state, and how Extentos abstracts the toolkit.
Get started
Start building with Extentos, the smart-glasses dev layer for Meta Ray-Ban. The recommended path is agent-driven — install the MCP server, it scaffolds the app.
Quickstart with an AI agent
Install the Extentos MCP server and let your AI agent scaffold Meta Ray-Ban smart-glasses capabilities into a native iOS or Android app. Free to start.