Android API reference
Symbol-level reference for the Extentos Android SDK — ExtentosGlasses, ExtentosConfig, every typed sub-client, the assistant builder, and the core data types, with exact Kotlin signatures.
Exact public signatures for com.extentos:glasses. Defaults shown are the real ones — where a parameter has a default you can omit it.
For how to use each area the SDK guides read better: initialization, lifecycle, threading. Error variants and payloads are in the generated error reference.
Entry point
public class ExtentosGlasses {
public companion object {
public fun create(config: ExtentosConfig = ExtentosConfig()): ExtentosGlasses
}
// Transport outcome — fixed for the life of the handle
public val transportChosen: TransportChosen
public val selectionSource: TransportSelectionSource
// Sub-clients
public val connection: ConnectionClient
public val camera: CameraClient
public val audio: AudioClient
public val display: DisplayClient
public val runtime: RuntimeClient
public val toggles: ToggleClient
public val voice: VoiceClient
public val assistant: AssistantClient
public val telemetry: TelemetryClient
public val observability: ObservabilityClient
public val device: DeviceIdentityClient
public val connectionConfig: ConnectionConfigSource
public val capabilities: GlassesCapabilities // computed on every read
public val usedCapabilities: Set<CapabilityKind>
public suspend fun shutdown()
}The constructor is internal — create(...) is the only way in. The property is toggles; its type is ToggleClient (singular).
There is no root connect() on Android — go through glasses.connection.connect().
ExtentosConfig
public data class ExtentosConfig(
val appId: String? = null,
val accountId: String? = null,
val transport: TransportChoice = TransportChoice.Auto,
val logLevel: LogLevel = LogLevel.WARN,
val preferredStreamConfig: PreferredStreamConfig? = null,
val debug: Boolean = false,
val usedCapabilities: Set<CapabilityKind> = emptySet(),
val telemetryConsent: Boolean = true,
val dataSharingConsent: Boolean = true,
val environment: ExtentosEnvironment = ExtentosEnvironment.DEVELOPMENT,
val telemetryEndpoint: String? = null,
val applicationContext: Context? = null,
val activityProvider: (() -> Activity?)? = null,
val hasBondedMetaDevice: (() -> Boolean)? = null,
)
public sealed interface TransportChoice {
public data object Auto : TransportChoice
public data object RealMeta : TransportChoice
public data object SystemAudio : TransportChoice
public sealed interface Simulated : TransportChoice {
public data object Local : Simulated
public data class Browser(val url: String? = null) : Simulated
}
}
public enum class LogLevel { VERBOSE, DEBUG, INFO, WARN, ERROR }debug is a config field defaulting to false — not your BuildConfig.DEBUG build type. See runtime internals for what it changes.
Sub-clients
ConnectionClient
public val state: StateFlow<GlassesState>
public val simulatorHint: StateFlow<SimulatorHint?>
public suspend fun connect(deviceId: DeviceId? = null): ExtentosResult<Unit, ConnectError>
public suspend fun disconnect()
public suspend fun reconnect(deviceId: DeviceId? = null): ExtentosResult<Unit, ConnectError>DeviceId is typealias DeviceId = String.
CameraClient
public suspend fun capturePhoto(config: PhotoConfig = PhotoConfig()): ExtentosResult<Photo, CaptureError>
public suspend fun captureVideo(config: VideoConfig = VideoConfig()): ExtentosResult<VideoClip, CaptureError>
public suspend fun stopVideo()
public fun videoFrames(config: VideoFrameConfig = VideoFrameConfig()): Flow<VideoFrame>
public fun activeStreamInfo(): ActiveStreamInfo? // a function; null when nothing is armed
public fun streamState(): Flow<CameraStreamState> // the activity axis
public var preferredStreamConfig: PreferredStreamConfig?streamState() reports what the camera stream is doing — IDLE, ARMING, STREAMING, PAUSED, STOPPING. It emits the current phase immediately on subscription, then only on real transitions, so every value is something that changed. Pair it with activeStreamInfo(), which reports the config a running stream locked at.
This is a different axis from the connection state's camera: CameraStatus, which is health (will a capture work, and should the UI prompt the wearer). Both matter at once: a latched capture failure while a stream is running is a real state, which is why they are not one enum.
Follow streamState() — not the connection state — for anything that forwards frames onward, such as a live relay, a recorder, or a frame pipeline. A stream can stop delivering while the connection stays perfectly healthy: the wearer folds the glasses, taps the temple, or another app takes the device session. PAUSED is the case with no other signal at any layer, because frames simply stop.
glasses.camera.streamState().collect { phase ->
when (phase) {
CameraStreamState.STREAMING -> relay.resume()
CameraStreamState.PAUSED -> relay.hold("folded, or another app took the camera")
CameraStreamState.IDLE -> relay.stop()
else -> Unit
}
}videoFrames is the one primitive that reports failure by throwing into the flow rather than returning ExtentosResult — a Flow has no error channel, so collect is where the reason arrives. Two exceptions, both at collection start:
| Exception | Meaning |
|---|---|
CameraStreamPausedException | The wearer paused the camera with the temple button. Prompt a tap and retry. A pause mid-stream is not an error — frames halt and resume. |
CameraUnavailableException | This transport has no camera path, so no frame will ever arrive. Carries .error, the same typed CaptureError capturePhoto would return. The usual cause on Android is a build declaring camera without com.extentos:glasses-meta — com.extentos:glasses is vendorless, so camera needs a vendor transport. |
try {
glasses.camera.videoFrames().collect { render(it) }
} catch (e: CameraUnavailableException) {
Log.e(TAG, "no camera on this transport: ${e.error}", e)
} catch (e: CameraStreamPausedException) {
showHint("Tap the right temple of your glasses to resume the camera")
}To branch before starting a stream, read glasses.capabilities.camera.
AudioClient
public suspend fun recordDiscrete(config: AudioRecordConfig = AudioRecordConfig()): ExtentosResult<AudioRecording, AudioError>
public fun audioChunks(config: AudioChunkConfig = AudioChunkConfig()): Flow<AudioChunk>
public fun transcriptions(config: TranscriptionConfig = TranscriptionConfig()): Flow<Transcript>
public suspend fun speak(text: String, config: SpeakConfig = SpeakConfig()): ExtentosResult<Unit, AudioError>
public suspend fun cancelSpeak()
public suspend fun earcon(sound: EarconSound, volume: Double = 1.0)
public suspend fun sendAudio(pcm16: ByteArray, sampleRate: Int): ExtentosResult<Unit, AudioError>
public suspend fun stopAudio()
public val outputFidelity: OutgoingAudioFidelity
// Extension, not a member:
public fun AudioClient.startPushToTalk(
scope: CoroutineScope,
config: TranscriptionConfig = TranscriptionConfig(language = "en-US", partial = false),
): PushToTalkSessionVoiceClient
public val hints: StateFlow<List<VoiceHint>>
public val stats: StateFlow<Map<String, VoiceHintStats>>
public fun onPhrase(
phrase: String,
label: String? = null,
stops: List<String> = emptyList(),
firesWhen: VoiceScope = VoiceScope.WhenDormant,
handler: suspend () -> Unit,
): VoiceRegistration
public fun registerHint(phrase: String, label: String? = null, stops: List<String> = emptyList()): VoiceRegistration
public fun reportFired(id: String)
public enum class VoiceScope { Always, WhenDormant, WhenActive }
public interface VoiceRegistration { public fun cancel() }firesWhen is Android-only — the iOS onPhrase has no scope parameter.
AssistantClient
public suspend fun start(provider: AssistantProvider, block: AssistantConfigBuilder.() -> Unit): AssistantSession
public fun createSession(config: AssistantConfig): AssistantSession
public suspend fun stop()
public val activeSession: AssistantSession?start(...) throws AssistantException on an open-time failure — it does not return an ExtentosResult. See the voice-assistant guide.
The rest
// RuntimeClient
public val events: Flow<RuntimeEvent>
public suspend fun snapshotEvents(): List<RuntimeEvent>
// ToggleClient
public val state: StateFlow<Map<String, JSONValue>>
public fun put(key: String, value: JSONValue, source: ToggleSource = ToggleSource.UI)
public fun get(key: String): JSONValue?
// TelemetryClient
public fun setUserSegment(segment: String?)
public fun trackEvent(name: String, properties: Map<String, Any?> = emptyMap())
public val consent: Boolean
// ObservabilityClient — wrap your own AI calls so they land in the event log
public suspend fun <T> aiCall(label: String, metadata: Map<String, String> = emptyMap(), block: suspend () -> T): T
// DeviceIdentityClient
public val type: GlassesDeviceType
public val vendor: GlassesVendor
// ConnectionConfigSource
public fun cached(): JsonObject?
public suspend fun fetch(): JsonObject?
// DisplayClient — guard every call on isAvailable
public val isAvailable: Boolean
public suspend fun show(onBack: (() -> Unit)? = null, content: DisplayRootScope.() -> Unit)
public suspend fun clear()
public suspend fun prepareVideo(clip: VideoClip): Boolean
public suspend fun forgetHostedVideo(clip: VideoClip)
public suspend fun forgetHostedImage(photo: Photo)The assistant builder
Everything settable inside assistant.start(provider) { … }. The receiver type is AssistantConfigBuilder.
public var instructions: String = ""
public var startActive: Boolean = false
public var endOnIntent: Boolean = true
public var greeting: Greeting = Greeting.Default
public var historyCap: Int = 100
public var historyCompaction: HistoryCompaction = HistoryCompaction.Auto
public var compactionModel: String? = null
public var persistentMemory: Boolean = false
public var memoryUserId: String? = null
public var memoryStore: MemoryStore? = null
public var localConductFloor: Boolean = true
public var fallbackGreeting: String? = null
public fun sleepAfterSilence(duration: Duration) // kotlin.time.Duration; must be positive
public fun sleepOnPhrase(phrase: String) // additive
public fun onWake(block: (suspend AssistantSession.() -> Unit)?)
public fun onSleep(block: (suspend AssistantSession.() -> Unit)?)
// tool() — three public overloads
public fun AssistantConfigBuilder.tool(
name: String, description: String, blocking: Boolean = false,
body: suspend () -> ToolResult,
)
public inline fun <reified Args> AssistantConfigBuilder.tool(
name: String, description: String, blocking: Boolean = false,
noinline body: suspend (Args) -> ToolResult, // schema inferred from @Serializable
)
public fun AssistantConfigBuilder.tool(
name: String, description: String, schema: JsonObject, blocking: Boolean = false,
body: suspend (args: JsonObject) -> ToolResult,
)includeDeviceInfoTool, deviceInfoNote and withinSessionMemory are not on this builder. They exist only on the AssistantConfig data class, so setting them inside start(provider) { … } does not compile. Use the raw form:
assistant.createSession(AssistantConfig(provider = …, includeDeviceInfoTool = false)) then session.start(). Same on both platforms.
AssistantSession
public val config: AssistantConfig
public val state: StateFlow<AssistantState>
public suspend fun start()
public suspend fun wake()
public suspend fun sleep()
public suspend fun stop()
public suspend fun say(text: String)
public suspend fun greet(prompt: String? = null)
public suspend fun includeImage(uri: String, prompt: String? = null)
public suspend fun cancelSpeak()
public suspend fun setReasoningEffort(effort: ReasoningEffort)
public suspend fun setVoice(voice: String)
public suspend fun setModel(model: String)
public suspend fun updateInstructions(instructions: String)
public suspend fun sendVideoFrame(jpegBytes: ByteArray, mimeType: String = "image/jpeg")
public val modelSupportsVideoInput: Boolean
public fun conversationHistory(limit: Int = 100): List<Turn>
public fun clearHistory()
public fun appendHistory(turn: Turn)
public fun replaceHistory(turns: List<Turn>)
public enum class AssistantState {
Idle, Dormant, Activating, Active, Reconnecting, Sleeping, Stopping, Stopped
}Providers and results
public sealed class AssistantProvider {
// One type for every model — the MODEL ID picks the vendor:
// gpt-* OpenAI, grok-* xAI, gemini-* Google, local-* on-device.
public data class Managed(
val model: String? = null, // "local-auto" or a local-* id runs on-device
val voice: String? = null,
val turnDetection: TurnDetection = TurnDetection.ServerVad(),
val reasoningEffort: ReasoningEffort = ReasoningEffort.Low,
) : AssistantProvider()
public data class Mock(
val behavior: MockBehavior = MockBehavior.MatchToolDescriptions,
) : AssistantProvider()
// Deprecated since 2.0.0, still fully functional — same shape, same
// behaviour. Existing code compiles unchanged; migrate when convenient.
@Deprecated("Renamed to AssistantProvider.Managed")
public data class OpenAi(/* identical to Managed */) : AssistantProvider()
}
public enum class ReasoningEffort { Minimal, Low, Medium, High, Xhigh }
public sealed class TurnDetection {
public data class ServerVad(
val threshold: Double = 0.5,
val prefixPaddingMs: Int = 300,
val silenceDurationMs: Int = 500,
) : TurnDetection()
public object SemanticVad : TurnDetection()
}
public sealed class ToolResult {
public data class Ok(val output: String) : ToolResult()
public data class Err(val message: String) : ToolResult()
}
public sealed class Greeting {
public object Default : Greeting()
public data class Custom(val directive: String) : Greeting()
public object Off : Greeting()
}
// Unwrap a capture result inside a tool body, or bail with the SDK's own message.
// Constrained to CaptureError — it does NOT apply to AudioError or ConnectError.
public inline fun <T> ExtentosResult<T, CaptureError>.orToolError(
onError: (ToolResult.Err) -> Nothing,
): TData types
public data class GlassesCapabilities(
val camera: Boolean, val microphone: Boolean,
val speaker: Boolean, val display: Boolean,
)
public sealed interface CapabilityKind {
public data object Camera; public data object Microphone
public data object Speaker; public data object Location
public data object Notifications; public data object Display
public data class Other(val name: String) : CapabilityKind
}
public enum class TransportChosen { REAL_META, BROWSER_SIM, LOCAL_SIM, PROJECTED_XR, SYSTEM_AUDIO }
public enum class TransportSelectionSource {
BUILD_CONFIG, ENV_VAR, BONDED_DEVICES, FALLBACK_DEFAULT,
EXPLICIT_CONFIG, PAIRING, VENDOR_REGISTRY
}
public enum class EarconSound { CONFIRMATION, ERROR, NOTIFICATION, START, STOP, COMPLETE }
public data class Photo(
val uri: String?, val width: Int, val height: Int,
val format: PhotoFormat, val exif: String?,
)
public data class VideoClip(
val uri: String?, val durationMs: Long,
val format: VideoFormat, val width: Int, val height: Int,
)Photo.uri and VideoClip.uri are nullable — elvis them. Use the Photos.* helpers rather than parsing the URI yourself; the scheme differs between simulator and hardware.
Capture and audio config
public data class PhotoConfig(
val resolution: Resolution = Resolution.MEDIUM,
val format: PhotoFormat = PhotoFormat.JPEG,
val dedicatedCapture: Boolean = false,
)
public data class VideoConfig(
val maxDurationSeconds: Int? = null, val includeAudio: Boolean = true,
val format: VideoFormat = VideoFormat.MP4_HEVC,
val resolution: Resolution = Resolution.MEDIUM, val frameRate: Int = 24,
)
public data class VideoFrameConfig(
val frameRate: Int = 2, val resolution: Resolution = Resolution.LOW,
val format: VideoFrameFormat = VideoFrameFormat.JPEG,
)
public data class AudioRecordConfig(
val maxDurationSeconds: Int? = null, val silenceTimeoutSeconds: Double? = null,
val quality: AudioQuality = AudioQuality.STANDARD,
)
public data class AudioChunkConfig(val chunkMillis: Int = 20, val sampleRate: Int = 16000)
public data class TranscriptionConfig(val language: String = "en-US", val partial: Boolean = true)
public data class SpeakConfig(
val voice: String? = null, val rate: Double = 1.0, val pitch: Double = 1.0,
val volume: Double = 1.0, val waitForCompletion: Boolean = true,
)TranscriptionConfig.language is honored on the simulator transports and ignored on real glasses and the audio baseline — see audio streaming. SpeakConfig has no language field; platform TTS follows the device locale.
Events
public sealed interface RuntimeEvent {
public data class ToggleChanged(val key: String, val oldValue: JSONValue, val newValue: JSONValue, val source: ToggleSource) : RuntimeEvent
public data class CoexistenceWarning(val blocked: String, val reason: String) : RuntimeEvent
public data class Log(val level: LogLevel, val message: String, val payload: JSONValue? = null) : RuntimeEvent
public data class UnrecognizedUtterance(val rawTranscript: String) : RuntimeEvent
public data class Assistant(val event: AssistantEvent) : RuntimeEvent
}
public sealed interface AssistantEvent {
public data class SessionStarted(val provider: String, val model: String?, val voice: String?)
public data class SessionEnded(val reason: EndReason, val message: String? = null)
public data class UserSpoke(val transcript: String)
public data class AssistantSpoke(val transcript: String) // transcript — generation done, NOT playback
public object AssistantAudioStarted // started talking
public object AssistantAudioFinished // stopped talking
public data class ToolCalled(val name: String, val args: JsonObject, val callId: String)
public data class ToolResultEvent(val callId: String, val name: String, val output: String, val isError: Boolean, val durationMs: Long)
public data class Reconnected(val reason: ReconnectReason, val downtimeMs: Long)
public data class Error(val kind: String, val message: String)
public data class LocalModelRequired(val modelId: String, val reason: LocalModelRequirement.Reason, val spokenMessage: String)
public data class AutoModelResolved(val servedModelId: String, val isLocal: Boolean, val cloudReason: CloudFallbackReason?, val downloadTarget: String?)
public object WentDormant
}AutoModelResolved is how you verify an on-device session actually stayed on-device. LocalModelRequired.Reason is NotDownloaded or DoesNotFit.
AssistantAudioStarted / AssistantAudioFinished strictly alternate and are what to watch for "is it talking right now" — AssistantSpoke is the transcript and fires seconds earlier, because the model streams audio faster than real time. The finished edge spans a whole turn including any tool round trip inside it, and fires immediately on a barge-in. See knowing when the assistant has stopped talking.
Connection state
public sealed class GlassesState {
public object NotRegistered
public object Registered
public data class DeviceDiscovered(val deviceId: String)
public data class Connecting(val deviceId: String)
public data class Active(val state: ActiveState)
public data class Disconnected(val cause: DisconnectCause)
}Active means the transport is up, not that glasses are present — see sessions. DisconnectCause variants and payloads are in the error reference.
Foreground service
public open class GlassesForegroundService : Service() {
public companion object {
public const val CHANNEL_ID: String = "extentos_capture"
@JvmStatic @JvmOverloads
public fun start(context: Context, title: String? = null,
text: String? = null, smallIconResId: Int = 0)
@JvmStatic @JvmOverloads
public fun start(context: Context, serviceClass: Class<out GlassesForegroundService>,
title: String? = null, text: String? = null, smallIconResId: Int = 0)
@JvmStatic @JvmOverloads
public fun stop(context: Context,
serviceClass: Class<out GlassesForegroundService> = GlassesForegroundService::class.java)
}
}Start it after RECORD_AUDIO is granted — Android 14+ rejects a microphone-type foreground service started without the grant. Subclass it and override extentosForegroundServiceType for a camera-type service.
Local tier
Separate artifacts — see on-device models.
// com.extentos.glasses.local (com.extentos:glasses-local)
public object ExtentosLocalTier {
public fun register(context: Context) // before ExtentosGlasses.create(...)
public fun models(context: Context): List<LocalModelInfo>
public suspend fun download(
context: Context, modelId: String,
onProgress: (bytesDownloaded: Long, totalBytes: Long) -> Unit = { _, _ -> },
): Boolean
public fun delete(context: Context, modelId: String): Boolean
public fun autoChoice(context: Context): AutoChoice
public fun deviceFit(context: Context, modelId: String): DeviceFit
public data class LocalModelInfo(val id: String, val displayName: String, val totalBytes: Long, val isInstalled: Boolean)
public data class AutoChoice(val modelId: String?, val isLocal: Boolean, val cloudReason: CloudFallbackReason?, val downloadTarget: String?)
public data class DeviceFit(val requiredMb: Long, val availableMb: Long, val fits: Boolean)
}
// com.extentos.glasses.localvoice (com.extentos:glasses-local-voice)
public object ExtentosLocalVoice {
public fun register(context: Context) // registers BOTH the voice and the recogniser
public fun modelDirectory(context: Context): File
public fun sttModelDirectory(context: Context): File
}
// On-device voice (~330 MB) and on-device transcription (~73 MB). Identical
// contract: size up front, byte progress, resumable, deletable, and never
// downloaded unless the app asks. Until installed, the system voice /
// recogniser serves.
public object KokoroVoiceModel {
public val totalBytes: Long
public fun isInstalled(context: Context): Boolean
public fun delete(context: Context): Boolean
public suspend fun download(
context: Context,
onProgress: (bytesDownloaded: Long, totalBytes: Long) -> Unit = { _, _ -> },
)
}
public object ExtentosSttModel {
public val totalBytes: Long
public fun isInstalled(context: Context): Boolean
public fun delete(context: Context): Boolean
public suspend fun download(
context: Context,
onProgress: (bytesDownloaded: Long, totalBytes: Long) -> Unit = { _, _ -> },
)
}Every local-tier call takes a Context on Android; the Swift equivalents take none.
Source of truth
The library ships as a compiled artifact on Maven Central; its API surface and KDoc are what you consume. Signatures here are transcribed from the shipped source — if one disagrees with your IDE's completion, trust the IDE.
Related
Error reference
Every typed error the Extentos SDK can return — ConnectError, CaptureError, AudioError, TransportError, the ExtentosError umbrella, and the Meta-DAT DeviceSessionError — with their payload fields and meaning. Lifecycle operations return ExtentosResult<T, E> with these concrete failure variants rather than throwing; pattern-match them. Generated from the Rust core.
iOS API reference
The Extentos iOS SDK API reference — entry point, ExtentosConfig fields, and the GlassesUI surface (ExtentosConnectionPage, theming, escape hatch). Hand-maintained until the generated DocC reference replaces it.
Android SDK
Add Extentos to your Android app via Gradle. Smart-glasses capabilities for Meta Ray-Ban, Android 12+ (API 31), Kotlin, coroutines.
Initialize the Android SDK
Create ExtentosGlasses with ExtentosGlasses.create, configure ExtentosConfig (environment, applicationContext, transport), reach the sub-clients, and pattern-match ExtentosResult.
Error reference
Every typed error the Extentos SDK can return — ConnectError, CaptureError, AudioError, TransportError, the ExtentosError umbrella, and the Meta-DAT DeviceSessionError — with their payload fields and meaning. Lifecycle operations return ExtentosResult<T, E> with these concrete failure variants rather than throwing; pattern-match them. Generated from the Rust core.
iOS API reference
The Extentos iOS SDK API reference — entry point, ExtentosConfig fields, and the GlassesUI surface (ExtentosConnectionPage, theming, escape hatch). Hand-maintained until the generated DocC reference replaces it.