CameraCharacteristics keysLiveData and Compose recompositionTextureView camera preview within a Jetpack Compose UIAndroidViewModel for camera lifecycle management. All source files are in app/src/main/java/com/oculus/camerademo/.| File | What it demonstrates | Key concepts |
|---|---|---|
MainActivity.kt | Jetpack Compose UI with camera preview integration | TextureView wrapped in AndroidView composable, LiveData observation, Material3 components |
CameraDemoViewModel.kt | Complete Camera2 lifecycle management | Camera discovery, session configuration, repeating capture requests, background thread handling |
QuestCameraHelpers.kt | Quest-specific camera characteristic keys | Custom CameraCharacteristics.Key definitions for position and source metadata |
PermissionManager.kt | Dual permission handling for Quest cameras | Android and HorizonOS permission checking, ActivityResultContracts integration |
ImageProcessHelpers.kt | Frame-by-frame image processing | YUV_420_888 format handling, Y-plane brightness calculation |
DataModels.kt | Data architecture for camera state | Sealed classes for events, enums for camera position, UI state models |
Utils.kt | Logging utilities | Consistent logging patterns |
CameraCharacteristics keys that extend the standard Camera2 API. QuestCameraHelpers.kt defines two vendor-specific keys:val KEY_SOURCE = Key("com.meta.extra_metadata.camera_source", Int::class.java)
val KEY_POSITION = Key("com.meta.extra_metadata.position", Int::class.java)
CameraConfig data class. The sample then selects a camera by position (defaulting to the right camera), and the isPassthrough flag is available for your own filtering logic.PermissionManager.kt to track both android.permission.CAMERA and horizonos.permission.HEADSET_CAMERA. The activity requests both permissions together using ActivityResultContracts.RequestMultiplePermissions(), and camera access only proceeds when both grants succeed.CameraDemoViewModel.kt, the session uses SESSION_REGULAR mode with two OutputConfiguration targets, one for the preview surface (displayed in TextureView) and one for the ImageReader surface (processed for brightness). The sample uses a repeating capture request, which continuously captures frames, targeting both surfaces so every frame reaches both the preview and the processing pipeline.ImageReader delivers a new frame, the callback runs on imageReaderThread and calls acquireLatestImage() to skip intermediate frames and get the most recent. The getBrightness() function reads the Y-plane buffer from the YUV_420_888 image, averages all pixel values, and posts the result to the main thread via Handler, where it updates LiveData and triggers Compose recomposition.CameraDemoViewModel.kt to support dynamic camera switching between left and right passthrough cameras at runtime.ImageProcessHelpers.kt that detects edges or highlights in the camera feed.