Skip to content

android(test app): MainActivity wrapper requests CAMERA at onCreate#359

Open
leaiss wants to merge 1 commit into
feat/android-permission-auditfrom
feat/android-permission-wrapper-activity
Open

android(test app): MainActivity wrapper requests CAMERA at onCreate#359
leaiss wants to merge 1 commit into
feat/android-permission-auditfrom
feat/android-permission-wrapper-activity

Conversation

@leaiss
Copy link
Copy Markdown
Collaborator

@leaiss leaiss commented May 28, 2026

Summary

Pick-up of the deferred follow-up from #350. Adds a thin Kotlin MainActivity subclass of NativeActivity whose only job is to call requestPermissions(CAMERA) from onCreate. Stacked on top of #350 (the manifest declarations the wrapper depends on).

Why this matters

CNSDK's face tracker (libleiaSDK-faceTrackingInApp.so, loaded into the app's process by the DisplayXR runtime broker) opens the front camera during xrCreateSession. On Android 6+ a manifest <uses-permission> declaration is not sufficient for "dangerous" perms — the user must explicitly grant at runtime. Without the grant: camera open() silently fails, CNSDK never reports a face, all xrLocateViews calls return the default centered eye position. No crash, no log error — just permanently no head tracking, which on Lume Pad is the entire point of the platform.

Pre-this-PR the only workaround was adb shell pm grant ... documented in Step 0 of the bring-up checklist (#350). That works for emulator + developer-mode device but is not a real day-1 path for hardware.

How it works

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

super.onCreate(...) runs the full NativeActivity glue (spawns the android_main thread, sets up the ANativeActivity callbacks, attaches the window) before we kick off requestPermissions. The native init / xrCreateInstance chain runs in parallel with the user-facing dialog. By the time CNSDK reaches its leia_core_enable_face_tracking call at session start, the user has already decided.

Trade-offs

  • First-launch race: if the user is slow to tap Allow, CNSDK may try to open the camera before the grant lands. Same end-state as current behavior (no face), and re-launching after the grant is a clean second-light. Avoiding this fully would require blocking the native main thread until the permission decision comes back — more invasive than the value it'd add.
  • No onRequestPermissionsResult override: we don't need one. The native code never asks Java about the permission; it just calls into CNSDK which calls into the system camera. If the permission flips state mid-session, CNSDK's leia_core_enable_face_tracking either succeeds or no-ops — both are valid states.
  • Deny case: silent no-face, identical to no-grant-at-all. Not great UX but acceptable for a POC test app; a production app would need to inform the user.

Verification (Android-36 emulator)

$ adb shell pm uninstall com.displayxr.cube_handle_vk_android
$ adb install -r cube_handle_vk_android-debug.apk
$ adb shell dumpsys package com.displayxr.cube_handle_vk_android | grep CAMERA
      android.permission.CAMERA
$ adb shell am start -n com.displayxr.cube_handle_vk_android/.MainActivity
... GrantPermissionsActivity launched ...
$ adb shell pm grant com.displayxr.cube_handle_vk_android android.permission.CAMERA  # simulates "Allow"
$ adb logcat | grep cube_handle
  xrInitializeLoaderKHR -> XR_SUCCESS
  xrCreateInstance -> XR_SUCCESS
  xrCreateVulkanInstanceKHR -> XR_SUCCESS
  xrCreateVulkanDeviceKHR vk_result=-7   # known emulator wall

Same full sentinel chain as pre-wrapper. No regression in NativeActivity glue (android_main, APP_CMD_INIT_WINDOW, xrInitializeLoaderKHR all reached). The permission dialog launches as the GrantPermissionsActivity intent.

Files

  • test_apps/cube_handle_vk_android/build.gradle — Kotlin Android plugin, kotlin-stdlib dep, kotlinOptions { jvmTarget = 17 }.
  • test_apps/cube_handle_vk_android/src/main/java/.../MainActivity.kt — the wrapper (~10 lines of actual code, rest is the file's why-this-exists block comment).
  • test_apps/cube_handle_vk_android/src/main/AndroidManifest.xml — drop android:hasCode="false", swap android.app.NativeActivity.MainActivity. android.app.lib_name meta-data preserved (the wrapper inherits the native lib load path).
  • docs/getting-started/android-bringup-checklist.md — Step 0 rewritten: dialog is now the default day-1 path; adb pm grant becomes opt-in for non-interactive bring-up.

Test plan

  • On Lume Pad first launch, system permission dialog appears within ~1s of icon tap.
  • Tapping Allow results in dumpsys package ... | grep CAMERA showing granted=true.
  • Head tracking works at session start (face-driven cube parallax visible).
  • Tapping Deny results in no head tracking but no crash (graceful degrade — same as missing grant).

🤖 Generated with Claude Code

@leaiss leaiss requested a review from dfattal as a code owner May 28, 2026 16:48
Thin Kotlin NativeActivity subclass whose only job is to surface the
runtime CAMERA permission dialog. CNSDK's face tracker opens the front
camera during xrCreateSession; without a runtime grant on Android 6+
the open silently fails and head tracking permanently uses the default
centered eye position. The wrapper hands off to NativeActivity via
super.onCreate immediately, so native init / xrCreateInstance run in
parallel with the permission request — by the time the face tracker
needs the camera, the user has already decided.

Eliminates the `adb pm grant` step from the bring-up checklist's Step 0
for interactive day-1 use; pre-grant via adb remains documented for
non-interactive bring-up (CI, scripted soak tests).

Validated on the Android-36 emulator: GrantPermissionsActivity launched
at start, full chain xrCreateInstance / xrCreateVulkanInstance /
xrCreateVulkanDevice -> XR_SUCCESS unchanged, same expected emulator-
Vulkan wall at VK device-extension check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant