How to add Android XR smart glasses support to an existing Android app
Android XR glasses apps are not separate apps. They are a projected activity inside the phone app you already ship. The native Jetpack Projected route step by step, the field notes from building a transport against it, and how to decide whether your business logic should name a glasses vendor at all.
Android XR's glasses model has one property that decides the whole integration: your app does not run on the glasses. Audio glasses and display glasses use a projected model, and Google describes it as "a dedicated activity that runs within your phone's existing app… projected from the host device to the glasses" (first-activity guide).
So the unit of work is not a new app. It is an activity, a manifest entry, and hardware access through a different Context, inside the APK you already ship.
That leaves you with an architectural decision rather than a porting project. You can wire your app's logic directly to Jetpack Projected, which is a legitimate and reasonably short path and the one this article teaches first. Or you can treat the glasses as a capability behind an interface, so Android XR is one implementation rather than a thing your business logic names.
One fact belongs up front because it changes the calculus: you cannot ship an Android XR glasses app to consumers today. Play distribution for Android XR covers "immersive app experiences on XR headsets and wired XR glasses devices"; for audio and display glasses, Google's distribution page tells you to run on the emulator and "stay tuned for more updates on distribution in the future" (package and distribute). Everything below is preparation work. Preparation work is exactly where architecture decisions are cheap.
Which Android XR are you targeting?
Three device classes, and they do not share a development path. Google's device types page splits them:
| Class | Dev paths | Play distribution |
|---|---|---|
| XR headsets | Jetpack XR SDK, Unity, OpenXR, WebXR | Available |
| Wired XR glasses | Jetpack XR SDK, Unity, OpenXR, WebXR | Available |
| Audio glasses and display glasses | Jetpack XR SDK only | Not yet |
This article is about the third row. If you are building an immersive headset app you want a different doc set, and most "Android XR tutorial" material on the web is about that row rather than this one.
Terminology is still moving, which makes searching harder than it should be. Google's I/O 2026 announcement calls the category intelligent eyewear and names two kinds: audio glasses that "offer spoken help in your ear" and display glasses that "show you the information you need" (blog.google). The devices page uses "audio glasses and display glasses" as the device-class phrase. The documentation URLs still say ai-glasses. Treat them all as the same target.
Hardware: audio glasses first, in fall 2026, with Samsung and frames from Gentle Monster and Warby Parker. Nothing in this class is purchasable as of August 2026.
What an Android XR glasses app looks like architecturally
One APK, on the phone, with a second entry point:
Your existing Android app (one APK, installed on the phone)
│
├── MainActivity phone screen, unchanged
│
└── ProjectedMainActivity projected to the glasses
│
├── projected device context → glasses mic, camera, speaker, display
└── host device context → phone hardware
Four consequences worth internalising before you write code.
The glasses are a display and sensor surface, not a compute target. Your process, your memory, your threads and your battery cost all stay on the phone. There is no on-glasses runtime to reason about, and no second build variant.
There are two contexts, and picking the wrong one is silent. ProjectedContext.createProjectedDeviceContext(activity) gives you the glasses; ProjectedContext.createHostDeviceContext(activity) gives you the phone. Hardware must be constructed against the one you mean. Google's warning here is the single most useful line in the doc set, and it is easy to miss:
"When accessing hardware or resources that are specific to the host device (phone) in a hybrid app, you must explicitly select the correct context. Don't use
getApplicationContextbecause the application context can incorrectly return the glasses' context if a projected activity was the most-recently-launched component."
If your existing app has any applicationContext-based audio or camera setup, that is the line to audit first.
The projected context is a lifecycle object, not a handle you keep. It stays valid while ProjectedContext.isProjectedDeviceConnected() is true, and it is destroyed when the glasses disconnect. On reconnect you create a new one and re-initialise anything built against the old one (access hardware via the projected context).
A third-party glasses app implies an Android host. Google says the glasses pair with both Android and iOS phones, and that is true of the hardware and the first-party Gemini experience. It is not a documented third-party app path: a projected app is an Android Activity in an Android manifest, and there is no iOS host SDK for it. Treat iPhone pairing as a consumer fact, not a developer one.
What you need to add Android XR to an existing Android app
-
Android Studio, latest Canary. Google is blunt about it: "Check that you're using the latest Canary build of Android Studio. Other versions might not include Android XR tools." Install it alongside your stable Studio against its own SDK root rather than upgrading the toolchain your production builds depend on.
-
The Jetpack XR SDK dependencies. The glasses quickstart currently lists:
dependencies { implementation("androidx.xr.runtime:runtime:1.0.0-beta01") implementation("androidx.xr.glimmer:glimmer:1.0.0-alpha16") implementation("androidx.xr.glimmer:glimmer-google-fonts:1.0.0-alpha16") implementation("androidx.xr.projected:projected:1.0.0-alpha09") implementation("androidx.xr.arcore:arcore:1.0.0-beta01") }Check the release notes rather than the guide before you pin: as of 2026-08-06 the latest
androidx.xr.projectedis 1.0.0-alpha10 (2026-07-15) and Glimmer is 1.0.0-alpha16 (2026-07-29), and the guide's projected pin lags by a version. The core runtime and ARCore libraries reached beta in Developer Preview 4; projected and Glimmer are still alpha, which is the fact that should drive how you isolate them. -
A newer compileSdk than you probably run.
projected:1.0.0-alpha10wantedcompileSdk 36in our build. That is a real cost if the rest of your app is settled on 35, and a good reason to keep the dependency out of your main module. -
Two AVDs. A glasses virtual device pairs with a phone virtual device. Boot the phone first, then the glasses, and deploy your app to the phone. It is memory-hungry; budget for it.
-
Patience about distribution. There is no channel for this device class yet.
The native route: add a projected activity to your app
1. Declare the projected activity
<application>
<activity
android:name=".ProjectedMainActivity"
android:exported="true"
android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED"
android:label="Glasses experience">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.XR_PROJECTED_LAUNCHER" />
</intent-filter>
</activity>
</application>
requiredDisplayCategory is what makes the activity a projected one. The XR_PROJECTED_LAUNCHER category is what makes it reachable by Gemini voice launch by app name, and if you declare more than one, "Gemini selects the first one found in the manifest file."
2. Launch it yourself when you need to
val options = ProjectedContext.createProjectedActivityOptions(context)
val intent = Intent(context, ProjectedMainActivity::class.java)
context.startActivity(intent, options.toBundle())
This is the path that matters for an existing app, because your glasses surface usually starts from something happening in your product rather than from the wearer naming your app out loud.
3. Reach the glasses microphone through the projected context
val projected = ProjectedContext.createProjectedDeviceContext(activity)
val audioRecord = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
.setAudioFormat(audioFormat) // 16 kHz, mono or stereo
.setBufferSizeInBytes(bufferSize)
.setContext(projected) // the load-bearing line
.build()
audioRecord.startRecording()
Sample rate is 16 kHz. CAMCORDER is the unprocessed default; VOICE_RECOGNITION adds echo cancellation and VOICE_COMMUNICATION adds noise reduction, which is the choice you make once and regret later if you guess.
4. Reach the camera the same way
val projected = ProjectedContext.createProjectedDeviceContext(activity)
val cameraProviderFuture = ProcessCameraProvider.getInstance(projected)
cameraProviderFuture.addListener({
val provider = cameraProviderFuture.get()
val selector = CameraSelector.DEFAULT_BACK_CAMERA // the glasses' outward camera
if (!provider.hasCamera(selector)) {
Log.w(TAG, "The selected camera is not available.")
return@addListener
}
provider.bindToLifecycle(activity, selector, imageCapture)
}, ContextCompat.getMainExecutor(activity))
CameraX, with DEFAULT_BACK_CAMERA mapping to the glasses' primary outward camera inside the projected context. The hasCamera guard is not defensive boilerplate here; see the field notes.
5. Request consent per device, not per app
Declare the permission in the manifest, then request it at runtime. Android XR grants are per virtual device, so a phone grant is not a glasses grant. Google documents two paths.
From a projected activity, via the projected contract:
private val launcher = registerForActivityResult(ProjectedPermissionsResultContract()) { results ->
if (results[Manifest.permission.CAMERA] == true) initializeGlassesFeatures()
}
launcher.launch(listOf(
ProjectedPermissionsRequestParams(
permissions = listOf(Manifest.permission.CAMERA),
rationale = "Camera access is required to capture what you are looking at.",
)
))
Or from a phone activity, via the device-aware overload:
requestPermissions(arrayOf(Manifest.permission.CAMERA), REQ_GLASSES_CAMERA, projectedDeviceId)
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray, deviceId: Int
) { /* check deviceId == projectedDeviceId */ }
Read the release notes before you commit to either. ProjectedPermissionsResultContract is deprecated as of projected:1.0.0-alpha10, which also added requestPermissions() to ProjectedActivityCompat (release notes) while the permissions guide still teaches the deprecated contract. This is the part of the integration we would most strongly advise you not to spread across your codebase.
6. Speak, and know what is documented
TextToSpeech is the documented audio-out path, and Google's page covers only that. Raw PCM playback is not documented for this device class.
tts = TextToSpeech(this) { status -> /* SUCCESS or not */ }
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceId)
Undocumented does not mean unavailable, and that distinction is worth money if you have your own audio pipeline. See the field notes.
7. Draw something, on display glasses only
Display UI is Jetpack Compose Glimmer, Google's "design language and UI toolkit for building augmented Android XR experiences". Audio glasses render nothing, so branch on the device rather than assuming:
// From a coroutine: create() reaches the projected service, so it suspends.
lifecycleScope.launch {
val controller = ProjectedDeviceController.create(this@ProjectedMainActivity)
val hasDisplay = controller.capabilities.contains(CAPABILITY_VISUAL_UI)
}
Two documented preview limitations will cost you an afternoon if you meet them cold. "When your app launches a projected activity, it doesn't automatically turn on the glasses' display. We're planning to add this capability in the future." And "there is a known rendering issue that causes the glasses' display to briefly flash white when a projected activity starts."
8. Track the connection
ProjectedContext.isProjectedDeviceConnected(context, coroutineContext) // Flow<Boolean>
A Flow<Boolean>, not a boolean. It is also the validity signal for every projected context you hold.
9. Run it on the emulator
The glasses AVD simulates the touchpad ("just below the display area", with a Two Finger mode, right side being the lens end), voice input through your computer's default microphone, the display, and audio-only mode via the Glasses companion app. Boot the phone AVD first and deploy to it.
Two constraints to plan around: "Camera capture features in the Android XR Emulator aren't available yet", and toggling audio-only mode requires closing and cold-booting the phone emulator each time.
Field notes from building a transport against Jetpack Projected
We built an Android XR transport for our own SDK against projected:1.0.0-alpha10 and the AI-glasses emulator image on 2026-07-24 and 2026-07-25, with two paired AVDs and a headless, scripted pairing loop. Everything in this section is emulator-observed, not hardware-verified, because no Android XR glasses exist to buy. On an alpha platform the docs are a hypothesis and the emulator is the closest thing to a fact.
The emulator reports a camera it does not have. The projected feature flags say camera is available. The camera list is empty. That is why the hasCamera guard above matters, and it is why our own camera path returns an explicit typed refusal on this vendor rather than shipping an implementation written from documentation we had already watched be wrong twice.
The projected deviceId is dynamic per boot. It was 1 on one boot and 2 on the next. The stable key is the persistent id (companion:N). Resolve identity at connect time; never cache it across connects.
The link takes one to two minutes to come up after boot. During that window the projected device association already exists while the availability signal still reads false. A connect path that fail-fasts on the first false tells the user they have no glasses when they simply have warming-up glasses. Build the patient version.
A host-only permission grant is not a substitute for a per-device grant. We measured it unblocking the projected microphone for roughly 380 ms before delivery died. The per-device grant is load-bearing.
Both documented consent paths behaved differently than the docs describe, in version-dependent ways. The deprecated contract was a silent no-op on our image. The alpha10 compat call cannot be invoked from the main thread by construction: decompiled, it blocks the calling thread waiting on a service connection that is delivered on that same thread, so a main-thread call deadlocks into its own timeout. It is callable from a background dispatcher. We do not think there is a stable public recipe here yet, and we would revisit it at beta rather than build a product assumption on it.
A freshly granted permission can keep reading denied. Through the projected context, until the app process relaunches. If your consent UX assumes grant-then-proceed, it will look broken exactly once per user. Treat the first grant as a relaunch boundary.
Raw audio out works, though only from the projected context. An AudioTrack constructed in the projected device context reaches the glasses as a Bluetooth A2DP sink and plays. Host-context playback with setPreferredDevice does not reroute. Google documents text to speech and says nothing about raw playback, so this is a documentation gap rather than a platform prohibition, and it is what makes a custom voice or a local synthesis pipeline viable on this platform.
Smaller drift, all cost a rebuild. ExperimentalProjectedApi moved into androidx.xr.projected.experimental. Several APIs the docs still name are now Kotlin-internal. The requiredDisplayCategory string in the docs differs from the one the library's own activity uses. ProjectedDeviceController.getAudioDevices() threw an unimplemented RemoteException on our image.
None of this makes Android XR a bad platform. It makes it an alpha one, which is a different claim and a temporary one.
Android XR only, or smart glasses generally?
If Android XR is the only glasses platform you will ever target, stop here and go build. The native path above is a few hundred lines, and coupling directly to a platform you have decided to marry is not a mistake.
The question is what happens on the second platform, because the glasses space acquired a second consumer platform in 2026 and the two share nothing underneath. Here is what actually differs between the three vendors we have transports for, measured rather than assumed:
| Meta smart glasses | Android XR glasses | Brilliant Labs | |
|---|---|---|---|
| App model | Phone app, native vendor SDK | Phone app, projected activity | Phone app, direct BLE |
| Transport | Bluetooth via the Device Access Toolkit | Projected activity + projected contexts | BLE, plus a Lua bundle uploaded to the device on connect |
| Display toolkit | Vendor view builder, 600x600 | Compose Glimmer, 450x394 | 256x256 circle (Halo) |
| Input | Neural Band pinch and thumb-slide | Temple touchpad, three gestures | No focus traversal on the device at all |
| Consent model | App-level runtime permissions | Per virtual device grants | Bluetooth permission only |
| Distribution | Invite-gated release channels | No channel for this class yet | Ordinary app stores, nothing gated |
| Test substrate the vendor gives you | Real hardware, no emulator | Glasses AVDs, no hardware | Neither |
Nothing on that table is exotic, and none of it is a complaint about any vendor. It is just the observation that "take a photo and describe it out loud" reaches hardware through three genuinely different mechanisms, with three different consent models, three panel geometries and three test loops.
If glasses are one experiment, you write that feature once, for one vendor, and none of this matters. If glasses are becoming a durable capability of your mobile product, you write it three times and then maintain it three times, and the feature after it costs three times too. That is the cost that compounds, and it lands in your business logic rather than in a corner of your codebase.
Adding Android XR through Extentos
Extentos is the layer that exists so that cost stays in one place. It is a Kotlin and Swift SDK: your code subscribes to capability primitives, and a per-vendor transport underneath translates them to whatever that vendor's platform actually is. Android XR is one of those transports, not a special mode.
The consequence is that a feature is written once, against capabilities:
glasses.audio.transcriptions().collect { t ->
if (t !is Transcript.Final) return@collect
if ("what am i looking at" !in t.text.lowercase()) return@collect
when (val shot = glasses.camera.capturePhoto()) {
is ExtentosResult.Ok -> glasses.audio.speak(describe(shot.value))
is ExtentosResult.Err -> glasses.audio.speak("I couldn't get a photo.")
}
}
Nothing there names a vendor, a transport, a Context, or a device. There is no projected activity in it, no second context to pick, and no per-device consent call, because those are the transport's problem rather than the feature's.
Two of those calls refuse on the Android XR preview transport today, and we would rather show you that than a checkmark. transcriptions() is not wired on it: the emulator image ships no on-device speech model, so the SDK closes the flow with a message pointing you at audioChunks() (16 kHz mono PCM off the projected mic) and your own speech recognition. capturePhoto() returns a typed refusal for the reason in the field notes above. speak(), the microphone, earcons, raw PCM out and the display path all work on it. Both refusals become real paths, with no change to the snippet above, when the platform and the hardware allow it. That is what preview means here.
Where hardware genuinely differs you ask about the difference rather than about the brand:
if (glasses.display.isAvailable) {
showOnLens(route.next) // display glasses, either vendor
} else {
glasses.audio.speak(route.next) // audio glasses, either vendor
}
That guard is why a branch written for one vendor's display already covered a vendor whose display did not exist when it was written.
The honest Android XR status
This is the part to read carefully, because "supported" is doing a lot of work in most vendor tables and we would rather you not be surprised.
- Built and emulator-proven. The projected transport, connection and identity resolution, microphone capture, text to speech, earcons and raw PCM out, and a display path that translates our display tree to Glimmer and renders it through a projected activity the SDK ships.
- Continuous transcription is not wired on it, because the emulator image ships no on-device speech model. The flow fails with an actionable message rather than going quiet, and
audioChunks()plus your own recogniser is the route in the meantime. - Deliberately unpublished. The
:glasses-xrmodule is not on Maven Central, so you cannot add it to a build today. It carries alphaandroidx.xrdependencies that would forcecompileSdk 36on every consumer of the SDK, including the ones who only care about Meta. Withholding it is the point of the isolation, not an oversight. - Not hardware-verified, because there is no hardware. Every claim above is emulator-observed.
- Camera returns a typed refusal on this transport, for the reason in the field notes. When hardware exists that becomes a real camera path with no change to your code.
- Meta is unaffected. The XR module is not a dependency of the core, so a Meta-only app never inherits a byte of it.
What that buys you today is narrower than "Android XR support" and worth stating plainly: your app is written against a surface that already has a working XR transport behind it, you can test how your app behaves under an Android XR device identity right now without any Google dependency, and Meta smart glasses ship today. The Android XR status page tracks this row by row and will change before this post does.
Where each vendor actually stands
Four separate categories, kept separate on purpose:
| Meta | Android XR | Brilliant Labs | |
|---|---|---|---|
| Transport implemented | Yes | Yes | Yes |
| Reachable from a build | Yes | No, deliberately unpublished | Yes, ships inside the SDK |
| Verified on real hardware | Yes | No hardware exists | No, and no emulator either |
| Selectable in the simulator | Yes | Yes, audio and display | Yes, Halo and Frame |
| Can you ship to users | Yes, through gated release channels | No channel for this class | Yes, ordinary app stores |
Only Meta is production. Android XR and Brilliant are both preview, for opposite reasons: Android XR has an emulator and no purchasable glasses, Brilliant has purchasable glasses and no emulator. Full detail on vendors.
What stays vendor-specific
An abstraction that claimed otherwise would be lying to you. Panel geometry differs and does not scroll, it clips, so design for the smallest panel you target. Icon names resolve against different sets, and an unmapped name renders nothing rather than the wrong glyph. And back is a genuine asymmetry: Meta's mid-pinch reaches your back handler, while Google's touchpad gesture API carries exactly three callbacks (swipe forward, swipe backward, click) with no back among them, so on Android XR back can only arrive through the system back path. Build screens the wearer can leave by selecting something.
Testing Android XR glasses experiences without hardware
Google's route is the one described above: Canary Studio, a glasses AVD paired to a phone AVD, touchpad by mouse, voice through your host microphone, and no camera capture. It is the highest-fidelity option available for the projected model specifically, because it is the real projected stack.
Our route is different in kind. The Extentos browser simulator replaces the vendor transport entirely, so it simulates a device identity rather than a vendor SDK. Selecting Android XR needs no Google library, no XR module and no extra dependency: your app is told it is on Android XR audio or display glasses, the capability set is reported accordingly, and the display panel is drawn at that device's real geometry (450x394 for Android XR display glasses, against 600x600 for Meta Ray-Ban Display) so a layout you build there is one the glasses can hold.
The two answer different questions. Google's emulator tells you whether your projected code works. The browser simulator tells you whether your app behaves correctly when the connected device is an Android XR one, which is the question you have far more often and the one you can answer before the platform is shippable.
Neither is hardware, and we would rather say that than imply otherwise. The rule we hold is that a simulator must never be more capable than the device, because that manufactures confidence instead of testing anything. Where hardware genuinely lacks a capability, the simulator refuses too: select a Brilliant Labs device and video refuses in simulation exactly as it does on the transport, because no effort makes video work over that link.
Android XR camera is the opposite case, and the distinction is worth carrying: gen-1 Android XR glasses do carry a camera, so the simulator surfaces one and your capture code runs there. Our projected transport still refuses capture, because the emulator exposes no camera to verify an implementation against. That refusal is a gap in our transport pending hardware, not a claim about the device, and those two things deserve different words.
When to use native Android XR instead
Use the native path when:
- Android XR is definitively your only glasses target. One platform, one integration, no reason for a seam.
- You need a projected API that no abstraction exposes. ARCore for Jetpack XR (verify what it actually gives you on this device class, since the richer perception story is documented for headsets and the Geospatial preview was announced for wired XR glasses), complications, a direct Gemini Live integration, or Glimmer UI richer than a shared cross-vendor display vocabulary can express. An abstraction cannot invent capabilities the layer beneath it does not have, and it should not pretend to.
- You want the alpha dependency in your own build, on your own terms, so you can move the moment Google opens distribution.
Consider the layer when:
- Glasses are a feature of an existing mobile product rather than a product of their own.
- More than one glasses platform matters, now or plausibly within a year.
- You want one test loop rather than one per vendor.
- The capabilities you need are camera, microphone, speaker and a small display, which is what nearly every glasses feature actually reduces to.
The honest failure mode of our approach is the "projected API that no abstraction exposes" case above. If your product's value lives in a vendor-specific capability, a shared vocabulary is in your way rather than under you, and you should go straight at the platform.
The decision, stated plainly
If you are evaluating Android XR, Google's native path is the right place to start, and it is short enough that you should just walk it. You will learn more from an afternoon with two AVDs than from any article, including this one.
If you are adding "smart glasses" as a lasting capability of a mobile application, the real question is not how to integrate Android XR. It is whether Android XR should become part of your application's architecture or merely one implementation behind it. Those are different commitments, and only one of them has to be made again for every vendor that ships after this one.
If you want the second shape, the Android quickstart is where an existing Android app starts, and the Android XR status page is where the preview status stays current. For the wider view, we maintain a neutral Android XR platform page alongside every other smart-glasses platform in the ecosystem reference.
Earlier posts on this platform: the developer story Samsung skipped when it revealed the hardware, and how we added Android XR as a second vendor without changing the app-facing API.
Frequently asked questions
Do Android XR smart glasses run my APK?
No. Audio glasses and display glasses use a projected model: your app stays on the paired phone, and you declare a dedicated activity whose UI and I/O are projected to the glasses. Google's wording is "a dedicated activity that runs within your phone's existing app." The compute and the app process stay phone-side, so adding glasses support means adding an activity to an app you already ship, not building a second app.
Can I publish an Android XR glasses app today?
Not to consumers. Google Play distribution for Android XR currently covers immersive experiences on XR headsets and wired XR glasses. For audio glasses and display glasses, Google's own distribution page directs developers to the emulator and says to "stay tuned for more updates on distribution in the future." Integration work you do now is preparation, not a release.
Can I test Android XR glasses without owning the hardware?
Yes, with limits. Google ships glasses AVDs that pair with a phone AVD in the Canary channel of Android Studio, and they simulate the temple touchpad, voice input, display and audio. Camera capture in the emulator is not available yet, so any camera path stays unproven until hardware exists. No emulator is evidence about a device.
Which Android XR APIs do glasses apps use?
The Jetpack XR SDK, and only that path for this device class. Jetpack Projected supplies the projected activity, the projected and host device contexts, and the connection signal; Jetpack Compose Glimmer supplies the display UI on display glasses. Google's device-types page lists OpenXR and WebXR for XR headsets and wired XR glasses, not for audio and display glasses.
Should I integrate Android XR directly or through a cross-vendor layer?
Directly, if Android XR is the only glasses platform you expect to support or you need a projected API that no abstraction exposes. Through a layer, if glasses are becoming a lasting capability of an existing mobile product and more than one vendor matters, because the per-vendor cost is a transport rather than a rewrite of your business logic. Extentos is that layer, and its Android XR transport is emulator-proven and deliberately unpublished while the platform is in developer preview.
Stay updated
New posts delivered to your inbox. No spam, low-volume, unsubscribe anytime.