Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions docs/getting-started/android-bringup-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,12 @@ If A passes you can stop. B confirms the CNSDK convention assumptions. C is only

The test app's `AndroidManifest.xml` declares `android.permission.CAMERA` (CNSDK front-camera face tracking) and `android.permission.WAKE_LOCK` (keep screen on during XR sessions). On Android 6+ `CAMERA` is a "dangerous" permission that requires either a user dialog OR an `adb pm grant`.

The test app is a thin `NativeActivity` with no Java/Kotlin wrapper to surface the runtime permission dialog. Two options for day-1:
The test app's `MainActivity` is a thin Kotlin wrapper around `NativeActivity` whose only job is to call `requestPermissions(CAMERA)` from `onCreate`. Day-1 path on Lume Pad: launch the app, tap **Allow** when the system permission dialog appears. The wrapper hands off to `NativeActivity` immediately, so the native init / `xrCreateInstance` runs in parallel — by the time CNSDK's face tracker opens the camera at session start, the grant is in place.

For non-interactive bring-up (CI, scripted soak tests, no human at the device), pre-grant via adb:

```bash
# Option 1: pre-grant via adb (works on emulator + device-with-developer-mode)
adb shell pm grant com.displayxr.cube_handle_vk_android android.permission.CAMERA

# Option 2: tap "Allow" on the system permission dialog the first time
# face tracking tries to open the camera (CNSDK throws the dialog itself
# on first leia_core_enable_face_tracking call — confirm at A1).
```

**Missing CAMERA on Lume Pad symptom:** session reaches FOCUSED, app renders, but `xrLocateViews` always returns the default centered eye position regardless of head motion. No crash, no log error from CNSDK — just silently no face tracking. Check `adb shell dumpsys package com.displayxr.cube_handle_vk_android | grep CAMERA` to confirm grant.
Expand Down
7 changes: 7 additions & 0 deletions test_apps/cube_handle_vk_android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}

android {
Expand Down Expand Up @@ -76,6 +77,10 @@ android {
targetCompatibility JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_17
}

packagingOptions {
jniLibs {
// libopenxr_loader.so ships in the AAR; keep it as-is
Expand All @@ -85,6 +90,8 @@ android {
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

// Khronos OpenXR loader for Android — bundles libopenxr_loader.so +
// openxr/*.h headers via prefab. The loader discovers our runtime
// APK at runtime via the OpenXRRuntimeService intent declared in
Expand Down
13 changes: 10 additions & 3 deletions test_apps/cube_handle_vk_android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,18 @@

<application
android:allowBackup="false"
android:label="DisplayXR Cube VK"
android:hasCode="false">
android:label="DisplayXR Cube VK">

<!--
MainActivity is a thin Kotlin wrapper around NativeActivity
that requests CAMERA at onCreate so CNSDK's face tracker
can open the front camera at xrCreateSession. The
android.app.lib_name meta-data is still required so the
wrapper inherits the native lib load path from
NativeActivity.
-->
<activity
android:name="android.app.NativeActivity"
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|keyboardHidden|screenSize"
android:launchMode="singleTask">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2026, Leia Inc.
// SPDX-License-Identifier: BSL-1.0
//
// Thin NativeActivity wrapper whose only job is to surface the runtime
// CAMERA permission dialog at first launch. CNSDK's face tracker
// (libleiaSDK-faceTrackingInApp.so, loaded into this process by the
// DisplayXR runtime broker) opens the front camera during
// xrCreateSession. On Android 6+ a manifest <uses-permission> is not
// sufficient for "dangerous" perms — the user must grant at runtime.
// Without this prompt, the camera open silently fails and head
// tracking permanently uses a default centered eye position.
//
// We delegate everything else (window setup, ANativeActivity glue,
// android_main thread spawn) to NativeActivity by calling
// super.onCreate first; the permission request is async and happens
// in parallel with the native init. If the user grants in time, the
// first xrCreateSession picks up the permission. If they deny or
// haven't decided yet, CNSDK degrades to no-face state — same as
// pre-wrapper behavior, but at least the dialog appeared.

package com.displayxr.cube_handle_vk_android

import android.Manifest
import android.app.NativeActivity
import android.content.pm.PackageManager
import android.os.Bundle

class MainActivity : NativeActivity() {

companion object {
private const val REQUEST_CAMERA = 1
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA)
}
}
}
Loading