Receive input events from glasses and Meta Neural Band

Updated: Oct 6, 2026

Overview

The Inputs capability delivers semantic input events from the wearer’s AI glasses and band to your app. Use it to drive menu navigation, confirm and cancel actions, respond to the capture and action buttons, or track a continuous drag interaction.
Beta: Inputs is a beta capability. It is available in the development and beta release channels only, and its API may change before general availability.
Inputs reports interactions, not raw sensor signal. You receive a decoded event such as “navigate down” or “select” rather than touch coordinates or muscle activity.
Type and value names below are given as Android / iOS where the two platforms spell them differently.
Every event carries the surface that produced it and the time it occurred:
FieldTypeNotes
source
Enum
Which surface produced the event. See Input sources.
timestampMs
64-bit integer
An on-device monotonic timestamp in milliseconds, recorded when the glasses forward the interaction. Use it for ordering and for measuring intervals within the same device boot, not as a wall-clock time, a raw hardware-event time, or a phone timestamp.
The capability delivers six event types:
Event (Android / iOS)Extra fieldsMeaning
Nav / .nav
direction: UP, DOWN, LEFT, RIGHT / .up, .down, .left, .right
Directional navigation.
Select / .select
None
Confirm or activate the focused item.
Back / .back
None
Dismiss or go back. Not currently delivered by real glasses. See Current limitations.
Button / .button
button: ACTION / .action
The physical action button was pressed.
Capture / .capture
pressType: SHORT_PRESS, HOLD, DOUBLE_PRESS / .shortPress, .hold, .doublePress
The physical capture button was pressed.
Drag / .drag
action, x, y, dx, dy
One event reporting the start, movement, or end of a drag interaction.
A Nav, Select, or Capture interaction produces exactly one event, emitted when the press is released. Do not expect separate down and up events for those; only Drag reports a multi-sample interaction. The action button is also reported as a single Button event per press, but see Current limitations for its hardware caveat.

Drag coordinates

A Drag event reports the phase of the interaction plus its position:
FieldMeaning
action
The phase of the drag: DOWN / .down begins the stream, MOVE / .move continues it, UP / .up ends it. A complete drag is one DOWN, zero or more MOVE samples, and one UP.
x, y
The absolute position of this sample, in the device’s input coordinate space.
dx, dy
The change in position since the previous sample of the same drag, in the same units.
The SDK does not guarantee a particular origin, range, or axis direction for the coordinate space, so treat the values as relative to each other rather than as screen or pixel coordinates.

Use this page with AI

Copy this prompt into your AI coding tool to add or debug an Inputs integration:
Use https://wearables.developer.meta.com/docs/develop/dat/inputs/, then use the Wearables MCP endpoint https://mcp.developer.meta.com/wearables to call search_dat_docs for current Device Access Toolkit Inputs guidance. Inspect my code first, then add the smallest working slice: add the Inputs capability to a started device session, consume the event stream, and observe capability state and errors. Remember that adding the capability begins activation with no separate start call, so wait for the active state before relying on event delivery. Handle every InputEvent type including an unknown-default branch on iOS, request only the input sources my feature needs, retain the listener tokens for as long as I want callbacks, and remove the capability during teardown.

Prerequisites

  • A working integration built from iOS integrations or Android integrations, including registration with the Meta AI app.
  • A device session in the started state. You cannot add Inputs to a session that has not started.
Inputs does not require a user-facing permission request. There is no Permission value for input data, so you do not call checkPermissionStatus or requestPermission before using it. See Permissions and registration for the permissions that do require a user prompt.

Input sources

Each event names the surface it came from, so you can treat the same semantic action differently depending on where it originated.
Source (Android / iOS)SurfaceEvents it produces
CAPTOUCH / .captouch
The touchpad on the temple arm of the glasses.
Nav, Select
NEURAL_BAND / .neuralBand
Discrete band inputs, such as a tap.
Nav, Select
CAPTURE_BUTTON / .captureButton
The physical capture (camera) button.
Capture
ACTION_BUTTON / .actionButton
The physical action button.
Button
NEURAL_BAND_DRAG / .neuralBandDrag
The continuous band drag stream.
Drag
UNKNOWN / .unknown
An unset or unrecognized surface.
Any
UNKNOWN is the SDK’s decoding fallback, not a delivery guarantee: if an unset or unrecognized source reaches the SDK, it is represented as UNKNOWN. Because the default configuration requests the five named sources and does not include UNKNOWN, source filtering can discard an event from a future or unrecognized surface before the SDK decodes it. Handle the UNKNOWN case so your code stays exhaustive, but do not rely on it to receive events from surfaces this SDK version does not know about.
Which physical input maps to which event is decided by the device, not by your app. A double tap on the touchpad arrives as Select, and a swipe arrives as Nav with the direction produced by the current device interaction mapping and settings. The wearer’s device settings can invert horizontal and vertical swipe output, so a forward swipe does not always produce the same direction. Treat the exact mapping as device behavior that can differ across device models and settings.

Interactions reserved by the device

While a Meta Wearables Device Access Toolkit session is running and the AI glasses are not showing interactive display content, the AI glasses reserve two one-finger touchpad interactions as on-device session controls. Your app never receives them:
InteractionDevice behavior
Single tap with one finger
Pauses or resumes the device session.
Tap and hold with one finger
Stops the device session.
Do not design an interaction around a single tap or tap and hold on the touchpad. Use a double tap to select, and use the capture button’s HOLD / .hold press type if you need the button’s hold action. Because tapping and holding with one finger stops the session, a user who taps and holds the temple arm ends your session rather than triggering an in-app action; handle the resulting session state change as you would any other external termination. See Session lifecycle.
This reservation is scoped to that display state. When the Meta Ray-Ban Display glasses are showing interactive display content the interaction mapping is different, and mappings can differ across device models, so do not build a feature that depends on any particular touchpad interaction reaching your app.

Choose which sources to capture

Pass an InputsConfiguration to request specific sources. By default the capability requests every named source, so subscribe more narrowly when your feature only needs part of the surface area.
SettingDefaultMeaning
sources
The five named sources; UNKNOWN is not included
The set of surfaces to capture. Events from a source you did not request are not delivered. An empty set (emptySet() / []) is a wildcard meaning every available source.
consumeBack
true
Whether a back interaction is delivered to your app instead of being handled by the system. Real glasses do not deliver Back yet. See Current limitations.
Requesting only what you need avoids consuming interactions that the system or on-glasses display would otherwise handle.

Capability lifecycle

Adding Inputs starts it: a successful call attaches Inputs and begins activation, and there is no separate start() call. Activation is asynchronous: an Android success result or an iOS return does not mean Inputs is already active. Wait for ACTIVE / .active before relying on event delivery or injecting Mock Device Kit events.
State (Android / iOS)Meaning
INACTIVE / .inactive
Not running. This is the initial state and the resting state after the capability stops.
ACTIVATING / .activating
Activation was requested but the device has not acknowledged it yet.
ACTIVE / .active
Events are being delivered as they occur.
DEACTIVATING / .deactivating
Teardown is in progress.
A clean removal winds down through DEACTIVATING to INACTIVE. An error, a device disconnect, or the wearer doffing the glasses also ends at INACTIVE.
Because state alone does not tell you why the capability stopped, use the error stream to distinguish the cases. Removing the capability and doffing the glasses both leave the error value unset; a failure reports a reason.
If activation is not acknowledged within 10 seconds, the capability reports an activation timeout and settles back to INACTIVE.
The event stream buffers the most recent 64 events on both platforms and drops the oldest when a consumer falls behind. Keep per-event work small, and hand heavier processing off to another task or coroutine so you do not lose input.

Error handling

ErrorMeaning
Permission denied
Your app is not permitted to receive input events from the glasses.
Connection closed
The connection delivering input events closed.
Activation timeout
Activation was not acknowledged within 10 seconds.
Activation failed
Activation was refused or is unsupported. Unlike a timeout, this is a definitive failure.
Device disconnected
The glasses disconnected while Inputs was active.
Communication error
A communication failure occurred on the input channel.
Capability unavailable
iOS only. The capability is not available on this device.
For the enumerated values behind this table, see InputsError in the API reference for Android and iOS.
Every error ends the current stream. There is no class of error you can recover from by immediately re-adding the capability, so repair the underlying condition first. A permission-denied error in particular will keep failing until the app is permitted to receive input events. Recovery then differs by platform:
  • Android. The SDK restores an attached Inputs instance itself after a session reconnect and clears the retained error, so you do not re-add it. Calling addInputs while an instance is still attached returns CAPABILITY_ALREADY_ADDED; call removeInputs() first if you deliberately want to replace the capability with a different configuration.
  • iOS. A published error stops delivery but leaves the capability attached to the session. Call removeInputs() before adding a replacement, or the next addInputs call throws an error.

Current limitations

These are limitations of the current beta, not of the API shape. Handle every event type defined by the SDK, including event types that real glasses do not currently deliver.
  • Back events are not currently delivered by real glasses. The Back event type exists in the SDK, but the on-glasses delivery path is not wired yet, so no Back event reaches your app from real hardware. Mock Device Kit can inject Back and honors consumeBack, so you can write and test your back handling today. Until real glasses deliver it, also provide another way to dismiss or go back, such as a Nav target or a Select on an explicit back item.
  • Volume interactions are not exposed. The system owns volume, and volume changes are not delivered as input events.
  • Action-button support is not validated on all hardware.Button with ACTION / .action is the only button event, and physical action-button support has not been validated on every target device. Treat it as unconfirmed on hardware you have not tested, and do not assume a press-and-hold of the action button produces an event.

Android

Step 1: Add the dependency

Add the inputs artifact alongside the core artifact in your version catalog, using the same version reference as the rest of the SDK.
[libraries]
mwdat-core = { group = "com.meta.wearable", name = "mwdat-core", version.ref = "mwdat" }
mwdat-inputs = { group = "com.meta.wearable", name = "mwdat-inputs", version.ref = "mwdat" }
dependencies {
  implementation(libs.mwdat.core)
  implementation(libs.mwdat.inputs)
}

Step 2: Add the capability to a started session

Call addInputs on a started DeviceSession. It returns a DatResult, so handle the failure branch rather than assuming success. A successful call attaches Inputs and begins activation; wait for ACTIVE before relying on events.
import com.meta.wearable.dat.inputs.Inputs
import com.meta.wearable.dat.inputs.addInputs
import com.meta.wearable.dat.inputs.types.InputSource
import com.meta.wearable.dat.inputs.types.InputsConfiguration

val configuration = InputsConfiguration(
    sources = setOf(InputSource.CAPTOUCH, InputSource.NEURAL_BAND),
)

val result = session.addInputs(configuration)
val inputs = result.getOrNull()
if (inputs == null) {
  showError(result.errorOrNull()?.description.orEmpty())
  return
}
Use getOrNull() and errorOrNull() to access the typed DeviceSessionError. You can also use fold or the two-parameter onFailure callback, which receives the typed error and its cause. A lambda is an unnamed function that you pass to another function. getOrElse takes one callback argument: a lambda that it calls on failure with one Throwable parameter. The Throwable type does not expose the DAT error’s description property, but it can contain a message and a cause.
Omit the configuration to request every named source with back consumption enabled.

Step 3: Collect events

Collect events, state, and errors on a coroutine scope that you cancel during teardown.
import com.meta.wearable.dat.inputs.types.InputEvent
import com.meta.wearable.dat.inputs.types.NavDirection

scope.launch {
  inputs.events.collect { event ->
    when (event) {
      is InputEvent.Nav ->
          when (event.direction) {
            NavDirection.UP -> moveFocusUp()
            NavDirection.DOWN -> moveFocusDown()
            NavDirection.LEFT -> moveFocusLeft()
            NavDirection.RIGHT -> moveFocusRight()
          }
      is InputEvent.Select -> activateFocusedItem()
      is InputEvent.Back -> dismiss()
      is InputEvent.Button -> onActionButton()
      is InputEvent.Capture -> onCapturePress(event.pressType)
      is InputEvent.Drag -> onDrag(event.action, event.x, event.y, event.dx, event.dy)
    }
  }
}
InputEvent is a sealed class, so an exhaustive when gives you a compile error if a future SDK version adds an event type.
Observe state and errors to know when events will flow and why they stopped.
import com.meta.wearable.dat.inputs.types.InputsState

scope.launch {
  inputs.state.collect { state ->
    if (state == InputsState.ACTIVE) onReceivingInput() else onNotReceivingInput()
  }
}

scope.launch {
  inputs.errors.collect { error ->
    if (error != null) showError(error.description)
  }
}
state and errors are state flows, so a new collector immediately receives the current value. errors holds null until something goes wrong and retains the last error afterward, so treat a repeated value as the same error rather than a new one. After a session reconnect the SDK restarts Inputs and resets errors to null.

Step 4: Remove the capability

removeInputs is a separate extension function, so it needs its own import.
import com.meta.wearable.dat.inputs.removeInputs

session.removeInputs()
Removing the capability stops input streaming and releases the input surfaces on the glasses. Do it as soon as your feature no longer needs input, rather than waiting for the session to end.

Test without hardware

Mock Device Kit injects synthetic events over the same path a real device uses, so your event-handling code cannot tell them apart. Events are delivered only while Inputs is streaming and only for sources the session requested; other calls are dropped.
val glasses = mockKit.pairGlasses(GlassesModel.RAYBAN_META).getOrThrow()
// ...after your app has added the Inputs capability and it has reached ACTIVE...
glasses.services.input.navDown()
glasses.services.input.select()
glasses.services.input.capture(CapturePressType.SHORT_PRESS)
Only the navigation, select, and back injections take a source, defaulting to CAPTOUCH, so you can simulate the same action arriving from the band. capture(), button(), and drag() take no source: their events always report the capture button, the action button, and the band drag stream respectively. See Android testing for how to create a mock device.

iOS

Step 1: Import the module

The inputs module ships with the SDK Swift package you added in iOS integrations. Import it wherever you use the capability.
import MWDATCore
import MWDATInputs

Step 2: Add the capability to a started session

addInputs returns nil if the session has not started, and throws if an Inputs capability is already attached. A successful return attaches Inputs and begins activation; wait for .active before relying on events.
let configuration = InputsConfiguration(sources: [.captouch, .neuralBand])
guard let inputs = try session.addInputs(configuration: configuration) else { return }

Step 3: Consume events

addInputs creates the Inputs object and begins activating the capability before the addInputs call returns, so register your listeners on the returned Inputs object immediately. Neither publisher replays, so an error published in the window before you register cannot be retrieved later; inputs.state always reports the current state.
Retain the returned listener tokens for as long as you want callbacks. Releasing a token cancels its listener, so a token stored in a local variable inside a setup method stops observation as soon as that method returns. Keep them in a ListenerTokenBag held by the same object that owns the capability.
private let listenerTokens = ListenerTokenBag()

inputs.statePublisher.listen { state in
  Task { @MainActor in
    // Update your UI for .activating, .active, .deactivating, or .inactive
  }
}.store(in: listenerTokens)

inputs.errorPublisher.listen { error in
  Task { @MainActor in
    switch error {
    case .permissionDenied:
      showPermissionHelp()
    case .connectionClosed, .activationTimeout, .activationFailed, .capabilityUnavailable,
         .deviceDisconnected, .communicationError:
      showError(error)
    @unknown default:
      showError(error)
    }
    // Every error leaves the capability attached: remove it before adding a replacement.
  }
}
events is an AsyncStream, so iterate it in a task that you retain and cancel during teardown.
let events = inputs.events
eventsTask = Task {
  for await event in events {
    switch event {
    case let .nav(direction, _, _):
      await moveFocus(direction)
    case .select:
      await activateFocusedItem()
    case .back:
      await dismiss()
    case .button:
      await onActionButton()
    case let .capture(pressType, _, _):
      await onCapturePress(pressType)
    case let .drag(action, x, y, dx, dy, _, _):
      await onDrag(action, x, y, dx, dy)
    @unknown default:
      break
    }
  }
}
InputEvent and InputsError are public enums that are not @frozen, so add an @unknown default branch to every switch over them. Without it your code stops compiling when a future SDK version adds a case.
Every case also exposes source and timestampMs directly on the event, so you can read them without unpacking the associated values.

Step 4: Remove the capability

eventsTask?.cancel()
try session.removeInputs()
Call this after an error too: a failed capability stays attached until you remove it.

Test without hardware

Mock Device Kit exposes the same injection functions as Android through the mock glasses services. As on Android, only the navigation, select, and back injections take a source.
mockGlasses.services.input.navDown()
mockGlasses.services.input.select()
mockGlasses.services.input.capture(pressType: .shortPress)
See iOS testing for how to create a mock device.

Mock Device Kit fidelity

Mock Device Kit produces the same event types your app receives from real glasses and delivers them over the same path, so your event-handling code cannot tell them apart. It also applies the same source and consumeBack filters, so an injected event from a source you did not request, or a Back injection when consumeBack is false, is dropped exactly as it would be on device.
It does not reproduce the device half of the pipeline. Input recognition, which physical surface a raw input is attributed to, which interactions the system reserves or passes through, duplicate-press handling, and per-model mappings are all device behavior that Mock Device Kit bypasses. Validate those on target hardware.
Timestamps are the one place the two are visibly different: the mock kits do not share a clock domain with a real device, and the iOS mock kit uses Unix epoch milliseconds where the Android mock kit uses a boot-relative clock. Assert on event ordering and intervals in tests, never on absolute timestamp values.

Platform differences

Inputs behaves the same on both platforms except in these places:
  • Adding the capability. Android returns a DatResult you unwrap. iOS returns an optional that is nil when the session has not started, and throws when a capability is already attached.
  • Event type. Android models InputEvent as a sealed class with a data class per event, so an exhaustive when is checked at compile time. iOS models it as a non-frozen enum with associated values, so switches need an @unknown default.
  • Error delivery. Android exposes errors as a Kotlin StateFlow, which holds a current value your app can observe. A collector is app code that calls collect to read that value and receive updates. A new collector receives the current value: a retained error, or null if no error is stored. It does not receive an error history. The SDK resets the value to null when Inputs restarts after a session reconnect. On iOS, a listener is an app callback registered with errorPublisher.listen. It receives errors published while it is registered. Errors published before registration are not sent again.
  • Error cases. iOS adds a capability-unavailable error that has no Android equivalent.
  • Recovery. Android restores an attached instance after a session reconnect and clears the retained error. On iOS the capability stays attached after an error and must be removed before a replacement is added.

Next steps