---
title: Initialize the Android SDK
description: Create ExtentosGlasses with ExtentosGlasses.create, configure ExtentosConfig (environment, applicationContext, transport), reach the sub-clients, and pattern-match ExtentosResult.
type: guide
platform: android
vendor: meta
related:
  - /docs/sdk/android/lifecycle
  - /docs/sdk/android/threading
  - /docs/reference/errors
  - /docs/sdk/android/install
---

<Callout type="warn">
  **Camera and display need the Meta vendor module.** `com.extentos:glasses`
  carries no vendor SDK, so add `implementation("com.extentos:glasses-meta")`
  alongside it — see [install](/docs/sdk/android/install). Without it the build
  still succeeds and voice still works, but `capabilities.camera` is `false` and
  captures return errors. The SDK logs a warning at startup when it spots that
  combination.
</Callout>


You create one `ExtentosGlasses` instance and hold it for the life of your app's glasses session. There is **no required `Application` subclass** and no static singleton to register — you call a factory and keep the handle.

## Create the instance

```kotlin
import com.extentos.glasses.core.ExtentosGlasses
import com.extentos.glasses.core.ExtentosConfig
import com.extentos.glasses.core.ExtentosEnvironment

val glasses = ExtentosGlasses.create(
    ExtentosConfig(
        applicationContext = applicationContext,
        environment = if (BuildConfig.DEBUG) {
            ExtentosEnvironment.DEVELOPMENT
        } else {
            ExtentosEnvironment.PRODUCTION
        },
    )
)
```

`ExtentosGlasses.create(config: ExtentosConfig = ExtentosConfig()): ExtentosGlasses` is the only entry point — the constructor is internal, so always go through `create()`. Calling it with no argument (`ExtentosGlasses.create()`) is valid and uses all defaults.

<Callout type="warn">
  **Camera or display apps need two more things, and the order matters.** The
  snippet above is the voice-app shape. `TransportChoice.Auto` picks the
  transport *inside* `create(...)` and keeps it for the life of the handle, so
  anything that influences the choice has to be true before you call it:

  1. **`BLUETOOTH_CONNECT` must already be granted.** The vendor arm claims by
     reading the bonded-device list, which that permission gates. Without it the
     check can't answer, `Auto` falls through to the audio baseline, and
     `capabilities.camera` reports `false` for the rest of the process — voice
     works, capture doesn't, and nothing throws.
  2. **Pass an `activityProvider`.** Meta's registration handoff needs a live
     Activity, and it defaults to `null`.

  So a camera app requests `RECORD_AUDIO` + `CAMERA` + `BLUETOOTH_CONNECT` from
  its Activity, *then* creates the handle. The worked bootstrap is in the
  [voice-assistant guide](/docs/guides/voice-assistant#if-your-app-also-uses-the-camera).
  Assert the result once at startup — `glasses.transportChosen` should read
  `REAL_META`, not `SYSTEM_AUDIO`.
</Callout>

A natural home for the handle is a `ViewModel` or a small holder scoped to your app's lifetime; create it once and reuse it. When you're done, call `glasses.shutdown()` (a `suspend` function) — see [Lifecycle](/docs/sdk/android/lifecycle).

## ExtentosConfig

Every field is defaulted, so the config is as small as you need it.

| Field | Default | Notes |
|---|---|---|
| `appId` | `null` | Your project id. Falls back to the host app's package name when null — that package is the project identity everywhere else (MCP, dashboard, telemetry), so leaving it null is usually fine. |
| `accountId` | `null` | Optional account attribution. |
| `transport` | `TransportChoice.Auto` | How to reach the glasses — see below. |
| `logLevel` | `LogLevel.WARN` | `VERBOSE` / `DEBUG` / `INFO` / `WARN` / `ERROR`. |
| `debug` | `false` | Enables the dev-loop pairing path under `Auto`. |
| `telemetryConsent` | `true` | When false, no events are sent at all. |
| `dataSharingConsent` | `true` | When false, your own dashboard still works but events are excluded from cross-account aggregates. |
| `environment` | `ExtentosEnvironment.DEVELOPMENT` | **Production apps must set `PRODUCTION`** — see the callout below. Use `BETA` for Play Console / TestFlight channels. |
| `telemetryEndpoint` | `null` | Override the default telemetry ingest endpoint. |
| `applicationContext` | `null` | **Required whenever the transport resolves to RealMeta or SystemAudio — i.e. on any real device** (Meta DAT needs a `Context`). Leave null only for headless JVM tests; on a real phone a null Context silently resolves to the in-memory `LocalSim` mock, which has no audio at all. |
| `activityProvider` | `null` | A `() -> Activity?` the SDK calls to get a foreground Activity for Meta registration. |
| `hasBondedMetaDevice` | `null` | A `() -> Boolean` the `Auto` chain uses to decide RealMeta vs. the audio baseline vs. simulator. The SDK has a built-in default, so you usually omit it. |
| `usedCapabilities` | `emptySet()` | The SDK capabilities your app uses (`Set<CapabilityKind>`). Values: `CapabilityKind.Camera`, `.Microphone`, `.Speaker`, `.Display`, `.Location`, `.Notifications`, and `CapabilityKind.Other(name)`. Drives the connection page's capability tiles — one tile per declared capability, lit when the connected glasses provide it, dimmed when they don't. The generated bootstrap sets this from your `generateConnectionModule(capabilities = [...])`. **On Android this is display-only** — the transport is picked from the bonded-device check, not from this set. On iOS the same field also decides the transport; if you ship both platforms, declare it on both. |

> **Set `environment = PRODUCTION` for production builds.** It defaults to `DEVELOPMENT` so that `installDebug` builds never pollute production analytics. If you ship a release build still on `DEVELOPMENT`, its events land in the dev backend, not your production bucket. The `BuildConfig.DEBUG` pattern above is the canonical way to wire it. (As a safety net the SDK also reconciles the declared environment against an on-device classifier and downgrades a mismatched `PRODUCTION` debug build with a warning — but declare it correctly rather than relying on that.)

### Transport choice

```kotlin
sealed interface TransportChoice {
    data object Auto : TransportChoice          // default — resolves at runtime
    data object SystemAudio : TransportChoice    // mic + speaker over OS Bluetooth; no vendor SDK
    data object RealMeta : TransportChoice       // force real glasses via Meta DAT
    sealed interface Simulated : TransportChoice {
        data object Local : Simulated            // in-process mock, no network
        data class Browser(val url: String? = null) : Simulated  // browser simulator
    }
}
```

`Auto` is almost always right. In order: the browser simulator when a session URL is baked in or set in the environment; then real glasses when a vendor module claims a bonded device; then — only when you set `ExtentosConfig.debug = true`, which is a config field defaulting to `false`, **not** your `BuildConfig.DEBUG` build type — the dev pairing screen; and otherwise `SystemAudio`, mic and speaker over whatever the OS has routed, which keeps a voice app working with no vendor SDK at all. A vendor claim beats the pairing screen, so a debug build with bonded glasses still goes to hardware. See [Runtime internals](/docs/sdk/android/runtime-internals) for the full resolution order and its caveats.

## Reading which transport you got

`ExtentosGlasses` exposes the resolution outcome directly — useful after an upgrade, or to confirm a release build reached real hardware rather than the audio baseline:

| Property | Type | What it tells you |
|---|---|---|
| `transportChosen` | `TransportChosen` | Which transport resolved: `REAL_META`, `SYSTEM_AUDIO`, `BROWSER_SIM`, `LOCAL_SIM` |
| `selectionSource` | `TransportSelectionSource` | Why it won. All seven: `EXPLICIT_CONFIG`, `BUILD_CONFIG`, `ENV_VAR`, `VENDOR_REGISTRY`, `BONDED_DEVICES`, `PAIRING`, `FALLBACK_DEFAULT` |
| `capabilities` | `GlassesCapabilities` | Flat yes/no facts of the connected device: `camera`, `microphone`, `speaker`, `display` |

```kotlin
Log.i("app", "transport=${glasses.transportChosen} (${glasses.selectionSource})")
```

A camera app that reads `SYSTEM_AUDIO` here got there for one of three reasons: `com.extentos:glasses-meta` isn't on the classpath, no Meta glasses are bonded to the phone, or `BLUETOOTH_CONNECT` wasn't granted before `create(...)`. Check them in that order.

## The sub-client surface

`ExtentosGlasses` exposes typed sub-clients as properties. You call capabilities through them:

| Property | What it does |
|---|---|
| `connection` | Connection state (`StateFlow<GlassesState>`), registration. |
| `camera` | `capturePhoto()`, `captureVideo()`, `videoFrames()`. |
| `audio` | `transcriptions()`, `audioChunks()`, `recordDiscrete(...)`, `speak(...)`. |
| `display` | Render UI on display-capable glasses; guard with `display.isAvailable`. |
| `runtime` | The on-device event log as `runtime.events: Flow<RuntimeEvent>`. |
| `toggles` | The runtime capability gates. |
| `voice` | Wake/voice-command hints and matching. |
| `telemetry` | App-level telemetry events. |
| `observability` | Diagnostics surface. |
| `assistant` | The end-to-end voice assistant _(`1.4.0+`)_. |
| `connectionConfig` | The dashboard-managed connection-page config. |
| `device` | Identity of the connected glasses (`device.type`, `device.vendor`). |
| `capabilities` | The flat capability facts of the connected device (camera/microphone/speaker/display). |

```kotlin
val photo = glasses.camera.capturePhoto()
glasses.audio.transcriptions().collect { transcript -> /* ... */ }
```

## Handle results with ExtentosResult

Lifecycle and capability operations return `ExtentosResult<S, F>` instead of throwing. It is a sealed interface with two cases:

```kotlin
import com.extentos.glasses.core.ExtentosResult

when (val r = glasses.camera.capturePhoto()) {
    is ExtentosResult.Ok  -> usePhoto(r.value)
    is ExtentosResult.Err -> handleFailure(r.error)
}
```

The failure side carries concrete, per-operation variants — `capturePhoto()` / `captureVideo()` return a `CaptureError` (`NotConnected`, `PermissionDenied`, `ThermalCritical`, `HingesClosed`, `CoexistenceBlocked`, `StreamPaused`, `DisabledByUser`, `PlatformError` — all eight, or your `when` won't be exhaustive); `connect()` returns a `ConnectError` (`NoDeviceAvailable`, `PermissionDenied`, `PairingFailed`, `RegistrationRequired`, `TransportFailure`); `recordDiscrete()` / `speak()` return an `AudioError`. See [Errors](/docs/reference/errors) for the full set.

> **Do not wrap these in `runCatching`.** They never throw, so `runCatching` would only catch unrelated exceptions and silently hide the typed failure. Pattern-match the result instead.

Helpers if you don't want a full `when`:

```kotlin
val value = glasses.camera.capturePhoto().valueOrNull()   // S? — null on failure
val error = result.errorOrNull()                          // F? — null on success
val mapped = result.map { it.uri }                        // transform the success
val chained = result.flatMap { upload(it) }               // chain another result
```

Next: [Lifecycle](/docs/sdk/android/lifecycle) covers shutdown, the foreground service, and process death; [Threading](/docs/sdk/android/threading) covers the coroutine/`Flow` model.
