{"owner":"android","repo":"camera-samples","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":4430,"categories":["root-instruction","mcp-config","marketplace","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"camera-samples":{"command":"npx","args":["-y","@modelcontextprotocol/server-camera-samples"]}}},"found":["AGENTS.md",".cursor/mcp.json",".claude-plugin/marketplace.json",".claude-plugin/plugin.json"],"skills":{"AGENTS.md":"# AGENTS.md — Android Camera Samples\n\nGuidance for AI coding agents (and humans) working in this repository. **Read this before making\nchanges.** It describes the rules, architecture, design system, and shared components.\n\n## What this is\n\nA single, **Compose-first** Android app — the **Camera Samples Catalog** — that showcases the\n**Camera2** and **CameraX** APIs through **28 small, self-contained samples**. A home catalog routes to\neach sample; every sample is its own Gradle library module and reuses a shared camera / UI / theme\nlayer, so adding a sample means writing *the feature*, not the boilerplate.\n\n## TL;DR — rules you must not violate\n\n1. **Viewfinder.** CameraX previews use **`CameraXViewfinder`**; Camera2 previews use the Compose\n   **`Viewfinder`**. **Never** use `PreviewView`, `ViewfinderView`, `SurfaceView`, or `TextureView`.\n   In practice always render through the shared `CameraXPreview` / `Camera2Preview` composables\n   (`:core-camera`) — there are **no Android Views** in sample/UI code.\n2. **One feature per sample.** Each sample demonstrates exactly one capability. Don't bundle features\n   unless a feature genuinely requires another (e.g. *Green Screen* = concurrent camera + ML Kit\n   segmentation, *Feature Combination* = several CameraX features by design).\n3. **No loose strings.** Every user-facing string lives in a module's `res/values/strings.xml` and is\n   read via `stringResource(...)` / `context.getString(...)`. No string literals in `Text(...)`,\n   `contentDescription`, button labels, etc.\n4. **Reuse the shared layers** (`:core-camera`, `:core-ui`, `:core-theme`). Don't reinvent shutter\n   buttons, control bars, scaffolds, previews, sliders, overlays, theme tokens, etc.\n5. **Layered architecture** (see below): `UiState` → `ViewModel` → `Controller` → `Screen`.\n   ViewModels never reference `Context`, `Activity`, lifecycle, or `Resources`.\n6. **Guard optional hardware** → render `UnsupportedView` instead of crashing.\n7. **Apache license header** on every file; run **`./gradlew spotlessApply`** (ktlint + headers)\n   before finishing.\n\n## Repository layout / module graph\n\n```\n:app                      home catalog + Navigation-Compose NavHost + Hilt application\n:core-theme               design system (colors, typography, AISampleCatalogTheme)        — leaf\n:core-camera              Camera2/CameraX plumbing (controllers, previews, utils)         — re-exports camera libs\n:core-ui                  shared Compose chrome (scaffold, controls, overlays, review)    — api(:core-camera, :core-theme)\n:samples:{api}-{feature}  one library module per sample                                   — depends on :core-camera + :core-ui\n```\n\nDependency direction is **acyclic**: `app → core-theme + samples`; `samples → core-camera + core-ui`;\n`core-ui → api(core-camera, core-theme)`; `core-camera` and `core-theme` are leaves.\n\n**Naming.** Module `samples/{api}-{feature}` → package `com.android.{api}.{feature}` → composables\n`{Api}{Feature}Screen` / `{Api}{Feature}ViewModel` / `{Api}{Feature}Controller` /\n`{Api}{Feature}UiState`, where `{api}` ∈ `{camerax, camera2}`.\n\n## Build, run, format\n\n- **JDK 17** toolchain (`jvmToolchain(17)`); **compileSdk / targetSdk 37** (Android 17), **minSdk 23**.\n- Build everything: `./gradlew assembleDebug` · Install: `./gradlew :app:installDebug`.\n- **Format (required, CI-gated):** `./gradlew spotlessApply` (auto-fix) / `./gradlew spotlessCheck`.\n  Spotless = ktlint + the Apache header from `spotless/copyright.kt`. `.editorconfig` sets\n  `ktlint_function_naming_ignore_when_annotated_with = Composable` so PascalCase `@Composable` names\n  pass.\n- Key versions (`gradle/libs.versions.toml`): AGP 9.2.1, Kotlin 2.4.0, Gradle 9.6, Compose BOM\n  2026.06.00, Material 3 1.5.0-alpha22, androidx.camera 1.6.1 (`camera-compose`,\n  `camera-viewfinder-compose`), Media3 1.10.1, Hilt 2.59.2, Navigation-Compose 2.9.8,\n  kotlinx-serialization 1.11.0, ML Kit (barcode 17.3, image-labeling 17.0.9, selfie-segmentation\n  16.0.0-beta6), KSP 2.3.5, Spotless 8.7.0.\n\n## Architecture & programming paradigm\n\nLayered, unidirectional (UDF), Compose-first — see [`android_architecture.md`](android_architecture.md).\nEach sample is **four pieces**:\n\n- **`{Feature}UiState`** — a `sealed interface` of states: `Initial`, one or more feature states\n  (e.g. `Previewing`, `Capturing`, `Recording`, `Captured`), and `Error(message)`. Add `Unsupported`\n  when hardware support is conditional.\n- **`{Feature}ViewModel`** — `@HiltViewModel`, `@Inject constructor`. Exposes **exactly one**\n  `val uiState: StateFlow<{Feature}UiState>` (a private `MutableStateFlow` surfaced immutably).\n  **No** `Context`/`Activity`/lifecycle/`Resources`; **no** events pushed to the UI (process the\n  event, update state). Business logic lives here.\n- **`{Feature}Controller`** — a `@Stable` state-holder that owns the **camera SDK lifecycle**\n  (open/close, sessions, capture/record). Created from the Screen via a `remember{Feature}Controller(...)`\n  composable. Camera2 controllers extend `BaseCamera2Controller`; CameraX controllers wrap\n  `ProcessCameraProvider`. The controller is the only place that holds `Context`.\n- **`{Feature}Screen`** — the screen-level composable. Collects with `collectAsStateWithLifecycle()`,\n  wraps content in `CameraSampleScaffold(...)`, and branches with `when (state)`.\n\n**Conventions**\n\n- Lifecycle-aware collection (`collectAsStateWithLifecycle`); never override Activity lifecycle methods\n  — use `LifecycleEventObserver` + `DisposableEffect`.\n- Every reusable composable (not the top-level Screen) takes `modifier: Modifier = Modifier` as its\n  **first optional** parameter.\n- Single-activity app + Navigation-Compose; the `NavHost` is derived from `sampleCatalog`.\n- **Single controller call-site for long-lived state.** When several states all show the camera\n  (`Previewing` / `Recording` / `Captured`), create the controller from **one** call site (e.g. a\n  shared `else ->` branch), not separately per `when` branch — otherwise a state change disposes and\n  recreates the controller and **kills an in-progress recording**. Overlay review screens on top of\n  the live content instead of swapping the whole branch.\n- DI: Hilt — `@HiltAndroidApp` Application, `@AndroidEntryPoint` MainActivity, ViewModels via\n  `hiltViewModel(...)`.\n\n## The viewfinder rule (expanded)\n\n- **CameraX** → render the `SurfaceRequest` through **`CameraXPreview`** (`:core-camera`), which uses\n  `androidx.camera.compose.CameraXViewfinder`.\n- **Camera2** → render through **`Camera2Preview`** (`:core-camera`), which uses\n  `androidx.camera.viewfinder.compose.Viewfinder` driven by a `BaseCamera2Controller`.\n- **Never** instantiate `PreviewView`, `ViewfinderView`, `SurfaceView`, or `TextureView`. Even the\n  captured-video player is Compose (Media3 `PlayerSurface`), not a `VideoView`.\n\n## Strings & resources\n\n- Each sample module owns its `res/values/strings.xml`. **No hardcoded user-facing text** anywhere.\n- Catalog strings live in `app/src/main/res/values/strings.xml` with keys\n  `{api}_{feature}_list_title` and `{api}_{feature}_list_description` (title format e.g.\n  `\"CameraX • Take a Photo\"`). In-screen strings use the sample module's own keys.\n\n## UI / design system — \"Console\"\n\nA dark, pro/technical aesthetic (`:core-theme` + `:core-ui`).\n\n- **Accent** violet `#B59CFF` (`primary`) on `#190F33` (`onPrimary`); dark surfaces; hairline borders.\n- **Type**: `displayFontFamily` / `bodyFontFamily` = Space Grotesk; `monoFontFamily` = Space Mono for\n  all technical/metadata text (API labels, filter chips, counts, HUD readouts).\n- **Home catalog** (`:app` `catalog/ui`): mono header + live count, the `SampleCategory` filter pills\n  (All · Images · Video · ML · Graphics · Extensions · Controls), a bordered featured hero\n  (`CatalogWideCard`), and a 2-column grid of compact tiles (`CatalogRowCard`) grouped under `CAMERAX`\n  / `CAMERA2` section labels. The tile API label is accent for CameraX, muted for Camera2; a category\n  dot uses the first tag's color.\n- **Viewfinder chrome** (`:core-ui`): scrim top-bar buttons (`ScrimIconButton`), centered title chip\n  (`ViewfinderTitleChip`), `RuleOfThirdsGrid`, the accent square `FocusIndicator` reticle, a bottom\n  `CameraControlsBar` with `ShutterButton` / `RecordButton`, `TorchChip` / `TorchGlow`, and\n  `ZoomControls`.\n- **In-app settings** use `SettingsOverlay` (+ `SettingsHeader` / `SettingsRow` / `SettingsDropdown`\n  accent chips / `settingsSwitchColors`) — never raw Material dropdowns.\n- **Captured-media review** via `CapturedImagePreview` / `CapturedVideoPreview`.\n\n## Component reference (reuse these — don't duplicate)\n\n### `:core-theme` (`com.android.camera.coretheme`)\n- **`AISampleCatalogTheme(darkTheme, contrast, content)`** — app theme wrapper (Material 3 +\n  `AppTypography`). `Theme.kt`\n- **Color tokens** (violet `primary`/`onPrimary`, dark surfaces, …) + **`extendedColorScheme`**\n  (`media3`, `mLKit`, `imagen`, `geminiProFlash`, … consumed by `SampleTags`). `Color.kt`, `Theme.kt`\n- **`displayFontFamily`**, **`bodyFontFamily`** (Space Grotesk), **`monoFontFamily`** (Space Mono),\n  **`AppTypography`**. `Type.kt`\n\n### `:core-camera` (`com.android.camera.core.*`)\n- **`BaseCamera2Controller`** — `@Stable` abstract base: background-thread open/close, session creation\n  (`createCaptureSession`), display-rotation transform, tap-to-focus (`focus`),\n  `surfaceRequest`/`transformationInfo` state, `release()`; subclasses override `onCameraOpened` /\n  `configureOutput` / etc. `camera2/BaseCamera2Controller.kt`\n- **`Camera2Preview(controller, modifier, onFocusTap)`** — Compose `Viewfinder` host. `camera2/Camera2Preview.kt`\n- **`CameraXPreview(surfaceRequest, modifier, onTapToFocus, onFocusTap)`** — `CameraXViewfinder` host\n  with coordinate transform. `camerax/CameraXPreview.kt`\n- **`ImageProxy.toBitmap(mirror)`** / **`Image.toBitmap(sensorOrientation, mirror)`** — JPEG → oriented\n  `Bitmap`. `image/ImageUtils.kt`\n- **`MediaStoreSaver`** — `saveBitmap(...)` to DCIM/Camera, `newVideoFile()`, `scanFile(...)`. `media/MediaStoreSaver.kt`\n- **`CameraPermissions`** — `PHOTO`, `VIDEO` permission lists. `permissions/CameraPermissions.kt`\n- **`rememberDisplayRotation(): Int`** — current display rotation; recomposes on config change. `display/DisplayRotation.kt`\n\n### `:core-ui` (`com.android.camera.coreui.*`)\n- **scaffold/** — `CameraSampleScaffold(permissions, api, …, content)` (permission gate + black surface\n  + API badge); `CameraApi` enum (`CAMERA2`, `CAMERAX`).\n- **controls/** — `ShutterButton`, `RecordButton`, `CameraSwitchButton`,\n  `ScrimIconButton(onClick, imageVector, …, size, iconSize)`, `CameraControlsBar(startSlot, center, endSlot)`,\n  `ValueSlider(label, value, …, accentColor)`, `ZoomControls(zoomRatio, valueRange, onValueChange, stops)`.\n- **overlay/** — `FocusIndicator(tapOffset)`, `RuleOfThirdsGrid()`, `ViewfinderTitleChip(text)`,\n  `TorchChip(on, onToggle)`, `TorchGlow(visible)`, `SettingsOverlay(visible, onDismiss, content)`,\n  `SettingsHeader(text)`, `SettingsRow(label, trailing)`,\n  `SettingsDropdown(label, options, selected, onSelected, optionLabel)`, `settingsSwitchColors()`.\n- **preview/** — `CapturedImagePreview(bitmap, onRetake, onDone)`, `CapturedVideoPreview(uri, onDismiss)`.\n- **widget/** — `VideoPlayer(player)` (Compose-first Media3 `PlayerSurface`; tap to play/pause),\n  `HdrWindowColorMode(enabled)` (toggle host-window HDR color mode, API 26+).\n- **state/** — `LoadingView()`, `ErrorView(errorMessage, onRetry)`, `UnsupportedView(message)`.\n\n## Catalog model (`app/.../catalog/domain/SampleCatalog.kt`)\n\n- **`sampleCatalog: List<SampleCatalogItem>`** — the single registry; the `NavHost` and the home grid\n  both derive from it.\n- **`SampleCatalogItem(title, description, route, sampleEntryScreen, type, category, tags, isFeatured, keyArt)`**.\n- **`SampleType`** = `{ CAMERAX, CAMERA2 }`; **`SampleCategory`** =\n  `{ IMAGES, VIDEO, ML, GRAPHICS, EXTENSIONS, CONTROLS }` (drives the home filter); **`SampleTags`** =\n  `{ MEDIA3, ML_KIT, VIDEO, EXTENSIONS, ANALYSIS }` (drives the tile's category dot color).\n\n## Adding a new sample\n\n1. **Generate**:\n   ```bash\n   ./gradlew createSample \\\n     -PsampleName=\"camera2-flash\" \\\n     -PscreenName=\"Camera2FlashScreen\" \\\n     -Ptitle=\"Camera2 • Flash\" \\\n     -Pdesc=\"Toggle the flash with Camera2\" \\\n     -Ptype=\"camera2\"          # camera2 (default) | camerax\n   ```\n   This scaffolds `samples/camera2-flash/` (UiState / ViewModel / Controller / Screen + `build.gradle.kts`\n   + `strings.xml`) and wires `settings.gradle.kts`, `app/build.gradle.kts`, the catalog\n   (`SampleCatalog.kt`), and the catalog strings.\n2. `./gradlew spotlessApply`.\n3. Implement the feature in the generated `Controller` + `Screen`; add in-screen strings to the\n   module's `strings.xml`; set the new `SampleCatalogItem`'s `category` (and `tags` / `isFeatured` if\n   relevant).\n4. Honor every TL;DR rule — reuse `:core-ui` / `:core-camera`, render via `CameraXPreview` /\n   `Camera2Preview`, no loose strings, one feature.\n5. Verify: `./gradlew assembleDebug spotlessCheck`.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/plugin.json)"},"files":{"AGENTS.md":"# AGENTS.md — Android Camera Samples\n\nGuidance for AI coding agents (and humans) working in this repository. **Read this before making\nchanges.** It describes the rules, architecture, design system, and shared components.\n\n## What this is\n\nA single, **Compose-first** Android app — the **Camera Samples Catalog** — that showcases the\n**Camera2** and **CameraX** APIs through **28 small, self-contained samples**. A home catalog routes to\neach sample; every sample is its own Gradle library module and reuses a shared camera / UI / theme\nlayer, so adding a sample means writing *the feature*, not the boilerplate.\n\n## TL;DR — rules you must not violate\n\n1. **Viewfinder.** CameraX previews use **`CameraXViewfinder`**; Camera2 previews use the Compose\n   **`Viewfinder`**. **Never** use `PreviewView`, `ViewfinderView`, `SurfaceView`, or `TextureView`.\n   In practice always render through the shared `CameraXPreview` / `Camera2Preview` composables\n   (`:core-camera`) — there are **no Android Views** in sample/UI code.\n2. **One feature per sample.** Each sample demonstrates exactly one capability. Don't bundle features\n   unless a feature genuinely requires another (e.g. *Green Screen* = concurrent camera + ML Kit\n   segmentation, *Feature Combination* = several CameraX features by design).\n3. **No loose strings.** Every user-facing string lives in a module's `res/values/strings.xml` and is\n   read via `stringResource(...)` / `context.getString(...)`. No string literals in `Text(...)`,\n   `contentDescription`, button labels, etc.\n4. **Reuse the shared layers** (`:core-camera`, `:core-ui`, `:core-theme`). Don't reinvent shutter\n   buttons, control bars, scaffolds, previews, sliders, overlays, theme tokens, etc.\n5. **Layered architecture** (see below): `UiState` → `ViewModel` → `Controller` → `Screen`.\n   ViewModels never reference `Context`, `Activity`, lifecycle, or `Resources`.\n6. **Guard optional hardware** → render `UnsupportedView` instead of crashing.\n7. **Apache license header** on every file; run **`./gradlew spotlessApply`** (ktlint + headers)\n   before finishing.\n\n## Repository layout / module graph\n\n```\n:app                      home catalog + Navigation-Compose NavHost + Hilt application\n:core-theme               design system (colors, typography, AISampleCatalogTheme)        — leaf\n:core-camera              Camera2/CameraX plumbing (controllers, previews, utils)         — re-exports camera libs\n:core-ui                  shared Compose chrome (scaffold, controls, overlays, review)    — api(:core-camera, :core-theme)\n:samples:{api}-{feature}  one library module per sample                                   — depends on :core-camera + :core-ui\n```\n\nDependency direction is **acyclic**: `app → core-theme + samples`; `samples → core-camera + core-ui`;\n`core-ui → api(core-camera, core-theme)`; `core-camera` and `core-theme` are leaves.\n\n**Naming.** Module `samples/{api}-{feature}` → package `com.android.{api}.{feature}` → composables\n`{Api}{Feature}Screen` / `{Api}{Feature}ViewModel` / `{Api}{Feature}Controller` /\n`{Api}{Feature}UiState`, where `{api}` ∈ `{camerax, camera2}`.\n\n## Build, run, format\n\n- **JDK 17** toolchain (`jvmToolchain(17)`); **compileSdk / targetSdk 37** (Android 17), **minSdk 23**.\n- Build everything: `./gradlew assembleDebug` · Install: `./gradlew :app:installDebug`.\n- **Format (required, CI-gated):** `./gradlew spotlessApply` (auto-fix) / `./gradlew spotlessCheck`.\n  Spotless = ktlint + the Apache header from `spotless/copyright.kt`. `.editorconfig` sets\n  `ktlint_function_naming_ignore_when_annotated_with = Composable` so PascalCase `@Composable` names\n  pass.\n- Key versions (`gradle/libs.versions.toml`): AGP 9.2.1, Kotlin 2.4.0, Gradle 9.6, Compose BOM\n  2026.06.00, Material 3 1.5.0-alpha22, androidx.camera 1.6.1 (`camera-compose`,\n  `camera-viewfinder-compose`), Media3 1.10.1, Hilt 2.59.2, Navigation-Compose 2.9.8,\n  kotlinx-serialization 1.11.0, ML Kit (barcode 17.3, image-labeling 17.0.9, selfie-segmentation\n  16.0.0-beta6), KSP 2.3.5, Spotless 8.7.0.\n\n## Architecture & programming paradigm\n\nLayered, unidirectional (UDF), Compose-first — see [`android_architecture.md`](android_architecture.md).\nEach sample is **four pieces**:\n\n- **`{Feature}UiState`** — a `sealed interface` of states: `Initial`, one or more feature states\n  (e.g. `Previewing`, `Capturing`, `Recording`, `Captured`), and `Error(message)`. Add `Unsupported`\n  when hardware support is conditional.\n- **`{Feature}ViewModel`** — `@HiltViewModel`, `@Inject constructor`. Exposes **exactly one**\n  `val uiState: StateFlow<{Feature}UiState>` (a private `MutableStateFlow` surfaced immutably).\n  **No** `Context`/`Activity`/lifecycle/`Resources`; **no** events pushed to the UI (process the\n  event, update state). Business logic lives here.\n- **`{Feature}Controller`** — a `@Stable` state-holder that owns the **camera SDK lifecycle**\n  (open/close, sessions, capture/record). Created from the Screen via a `remember{Feature}Controller(...)`\n  composable. Camera2 controllers extend `BaseCamera2Controller`; CameraX controllers wrap\n  `ProcessCameraProvider`. The controller is the only place that holds `Context`.\n- **`{Feature}Screen`** — the screen-level composable. Collects with `collectAsStateWithLifecycle()`,\n  wraps content in `CameraSampleScaffold(...)`, and branches with `when (state)`.\n\n**Conventions**\n\n- Lifecycle-aware collection (`collectAsStateWithLifecycle`); never override Activity lifecycle methods\n  — use `LifecycleEventObserver` + `DisposableEffect`.\n- Every reusable composable (not the top-level Screen) takes `modifier: Modifier = Modifier` as its\n  **first optional** parameter.\n- Single-activity app + Navigation-Compose; the `NavHost` is derived from `sampleCatalog`.\n- **Single controller call-site for long-lived state.** When several states all show the camera\n  (`Previewing` / `Recording` / `Captured`), create the controller from **one** call site (e.g. a\n  shared `else ->` branch), not separately per `when` branch — otherwise a state change disposes and\n  recreates the controller and **kills an in-progress recording**. Overlay review screens on top of\n  the live content instead of swapping the whole branch.\n- DI: Hilt — `@HiltAndroidApp` Application, `@AndroidEntryPoint` MainActivity, ViewModels via\n  `hiltViewModel(...)`.\n\n## The viewfinder rule (expanded)\n\n- **CameraX** → render the `SurfaceRequest` through **`CameraXPreview`** (`:core-camera`), which uses\n  `androidx.camera.compose.CameraXViewfinder`.\n- **Camera2** → render through **`Camera2Preview`** (`:core-camera`), which uses\n  `androidx.camera.viewfinder.compose.Viewfinder` driven by a `BaseCamera2Controller`.\n- **Never** instantiate `PreviewView`, `ViewfinderView`, `SurfaceView`, or `TextureView`. Even the\n  captured-video player is Compose (Media3 `PlayerSurface`), not a `VideoView`.\n\n## Strings & resources\n\n- Each sample module owns its `res/values/strings.xml`. **No hardcoded user-facing text** anywhere.\n- Catalog strings live in `app/src/main/res/values/strings.xml` with keys\n  `{api}_{feature}_list_title` and `{api}_{feature}_list_description` (title format e.g.\n  `\"CameraX • Take a Photo\"`). In-screen strings use the sample module's own keys.\n\n## UI / design system — \"Console\"\n\nA dark, pro/technical aesthetic (`:core-theme` + `:core-ui`).\n\n- **Accent** violet `#B59CFF` (`primary`) on `#190F33` (`onPrimary`); dark surfaces; hairline borders.\n- **Type**: `displayFontFamily` / `bodyFontFamily` = Space Grotesk; `monoFontFamily` = Space Mono for\n  all technical/metadata text (API labels, filter chips, counts, HUD readouts).\n- **Home catalog** (`:app` `catalog/ui`): mono header + live count, the `SampleCategory` filter pills\n  (All · Images · Video · ML · Graphics · Extensions · Controls), a bordered featured hero\n  (`CatalogWideCard`), and a 2-column grid of compact tiles (`CatalogRowCard`) grouped under `CAMERAX`\n  / `CAMERA2` section labels. The tile API label is accent for CameraX, muted for Camera2; a category\n  dot uses the first tag's color.\n- **Viewfinder chrome** (`:core-ui`): scrim top-bar buttons (`ScrimIconButton`), centered title chip\n  (`ViewfinderTitleChip`), `RuleOfThirdsGrid`, the accent square `FocusIndicator` reticle, a bottom\n  `CameraControlsBar` with `ShutterButton` / `RecordButton`, `TorchChip` / `TorchGlow`, and\n  `ZoomControls`.\n- **In-app settings** use `SettingsOverlay` (+ `SettingsHeader` / `SettingsRow` / `SettingsDropdown`\n  accent chips / `settingsSwitchColors`) — never raw Material dropdowns.\n- **Captured-media review** via `CapturedImagePreview` / `CapturedVideoPreview`.\n\n## Component reference (reuse these — don't duplicate)\n\n### `:core-theme` (`com.android.camera.coretheme`)\n- **`AISampleCatalogTheme(darkTheme, contrast, content)`** — app theme wrapper (Material 3 +\n  `AppTypography`). `Theme.kt`\n- **Color tokens** (violet `primary`/`onPrimary`, dark surfaces, …) + **`extendedColorScheme`**\n  (`media3`, `mLKit`, `imagen`, `geminiProFlash`, … consumed by `SampleTags`). `Color.kt`, `Theme.kt`\n- **`displayFontFamily`**, **`bodyFontFamily`** (Space Grotesk), **`monoFontFamily`** (Space Mono),\n  **`AppTypography`**. `Type.kt`\n\n### `:core-camera` (`com.android.camera.core.*`)\n- **`BaseCamera2Controller`** — `@Stable` abstract base: background-thread open/close, session creation\n  (`createCaptureSession`), display-rotation transform, tap-to-focus (`focus`),\n  `surfaceRequest`/`transformationInfo` state, `release()`; subclasses override `onCameraOpened` /\n  `configureOutput` / etc. `camera2/BaseCamera2Controller.kt`\n- **`Camera2Preview(controller, modifier, onFocusTap)`** — Compose `Viewfinder` host. `camera2/Camera2Preview.kt`\n- **`CameraXPreview(surfaceRequest, modifier, onTapToFocus, onFocusTap)`** — `CameraXViewfinder` host\n  with coordinate transform. `camerax/CameraXPreview.kt`\n- **`ImageProxy.toBitmap(mirror)`** / **`Image.toBitmap(sensorOrientation, mirror)`** — JPEG → oriented\n  `Bitmap`. `image/ImageUtils.kt`\n- **`MediaStoreSaver`** — `saveBitmap(...)` to DCIM/Camera, `newVideoFile()`, `scanFile(...)`. `media/MediaStoreSaver.kt`\n- **`CameraPermissions`** — `PHOTO`, `VIDEO` permission lists. `permissions/CameraPermissions.kt`\n- **`rememberDisplayRotation(): Int`** — current display rotation; recomposes on config change. `display/DisplayRotation.kt`\n\n### `:core-ui` (`com.android.camera.coreui.*`)\n- **scaffold/** — `CameraSampleScaffold(permissions, api, …, content)` (permission gate + black surface\n  + API badge); `CameraApi` enum (`CAMERA2`, `CAMERAX`).\n- **controls/** — `ShutterButton`, `RecordButton`, `CameraSwitchButton`,\n  `ScrimIconButton(onClick, imageVector, …, size, iconSize)`, `CameraControlsBar(startSlot, center, endSlot)`,\n  `ValueSlider(label, value, …, accentColor)`, `ZoomControls(zoomRatio, valueRange, onValueChange, stops)`.\n- **overlay/** — `FocusIndicator(tapOffset)`, `RuleOfThirdsGrid()`, `ViewfinderTitleChip(text)`,\n  `TorchChip(on, onToggle)`, `TorchGlow(visible)`, `SettingsOverlay(visible, onDismiss, content)`,\n  `SettingsHeader(text)`, `SettingsRow(label, trailing)`,\n  `SettingsDropdown(label, options, selected, onSelected, optionLabel)`, `settingsSwitchColors()`.\n- **preview/** — `CapturedImagePreview(bitmap, onRetake, onDone)`, `CapturedVideoPreview(uri, onDismiss)`.\n- **widget/** — `VideoPlayer(player)` (Compose-first Media3 `PlayerSurface`; tap to play/pause),\n  `HdrWindowColorMode(enabled)` (toggle host-window HDR color mode, API 26+).\n- **state/** — `LoadingView()`, `ErrorView(errorMessage, onRetry)`, `UnsupportedView(message)`.\n\n## Catalog model (`app/.../catalog/domain/SampleCatalog.kt`)\n\n- **`sampleCatalog: List<SampleCatalogItem>`** — the single registry; the `NavHost` and the home grid\n  both derive from it.\n- **`SampleCatalogItem(title, description, route, sampleEntryScreen, type, category, tags, isFeatured, keyArt)`**.\n- **`SampleType`** = `{ CAMERAX, CAMERA2 }`; **`SampleCategory`** =\n  `{ IMAGES, VIDEO, ML, GRAPHICS, EXTENSIONS, CONTROLS }` (drives the home filter); **`SampleTags`** =\n  `{ MEDIA3, ML_KIT, VIDEO, EXTENSIONS, ANALYSIS }` (drives the tile's category dot color).\n\n## Adding a new sample\n\n1. **Generate**:\n   ```bash\n   ./gradlew createSample \\\n     -PsampleName=\"camera2-flash\" \\\n     -PscreenName=\"Camera2FlashScreen\" \\\n     -Ptitle=\"Camera2 • Flash\" \\\n     -Pdesc=\"Toggle the flash with Camera2\" \\\n     -Ptype=\"camera2\"          # camera2 (default) | camerax\n   ```\n   This scaffolds `samples/camera2-flash/` (UiState / ViewModel / Controller / Screen + `build.gradle.kts`\n   + `strings.xml`) and wires `settings.gradle.kts`, `app/build.gradle.kts`, the catalog\n   (`SampleCatalog.kt`), and the catalog strings.\n2. `./gradlew spotlessApply`.\n3. Implement the feature in the generated `Controller` + `Screen`; add in-screen strings to the\n   module's `strings.xml`; set the new `SampleCatalogItem`'s `category` (and `tags` / `isFeatured` if\n   relevant).\n4. Honor every TL;DR rule — reuse `:core-ui` / `:core-camera`, render via `CameraXPreview` /\n   `Camera2Preview`, no loose strings, one feature.\n5. Verify: `./gradlew assembleDebug spotlessCheck`.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/plugin.json)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/android/camera-samples/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md — Android Camera Samples\n\nGuidance for AI coding agents (and humans) working in this repository. **Read this before making\nchanges.** It describes the rules, architecture, design system, and shared components.\n\n## What this is\n\nA single, **Compose-first** Android app — the **Camera Samples Catalog** — that showcases the\n**Camera2** and **CameraX** APIs through **28 small, self-contained samples**. A home catalog routes to\neach sample; every sample is its own Gradle library module and reuses a shared camera / UI / theme\nlayer, so adding a sample means writing *the feature*, not the boilerplate.\n\n## TL;DR — rules you must not violate\n\n1. **Viewfinder.** CameraX previews use **`CameraXViewfinder`**; Camera2 previews use the Compose\n   **`Viewfinder`**. **Never** use `PreviewView`, `ViewfinderView`, `SurfaceView`, or `TextureView`.\n   In practice always render through the shared `CameraXPreview` / `Camera2Preview` composables\n   (`:core-camera`) — there are **no Android Views** in sample/UI code.\n2. **One feature per sample.** Each sample demonstrates exactly one capability. Don't bundle features\n   unless a feature genuinely requires another (e.g. *Green Screen* = concurrent camera + ML Kit\n   segmentation, *Feature Combination* = several CameraX features by design).\n3. **No loose strings.** Every user-facing string lives in a module's `res/values/strings.xml` and is\n   read via `stringResource(...)` / `context.getString(...)`. No string literals in `Text(...)`,\n   `contentDescription`, button labels, etc.\n4. **Reuse the shared layers** (`:core-camera`, `:core-ui`, `:core-theme`). Don't reinvent shutter\n   buttons, control bars, scaffolds, previews, sliders, overlays, theme tokens, etc.\n5. **Layered architecture** (see below): `UiState` → `ViewModel` → `Controller` → `Screen`.\n   ViewModels never reference `Context`, `Activity`, lifecycle, or `Resources`.\n6. **Guard optional hardware** → render `UnsupportedView` instead of crashing.\n7. **Apache license header** on every file; run **`./gradlew spotlessApply`** (ktlint + headers)\n   before finishing.\n\n## Repository layout / module graph\n\n```\n:app                      home catalog + Navigation-Compose NavHost + Hilt application\n:core-theme               design system (colors, typography, AISampleCatalogTheme)        — leaf\n:core-camera              Camera2/CameraX plumbing (controllers, previews, utils)         — re-exports camera libs\n:core-ui                  shared Compose chrome (scaffold, controls, overlays, review)    — api(:core-camera, :core-theme)\n:samples:{api}-{feature}  one library module per sample                                   — depends on :core-camera + :core-ui\n```\n\nDependency direction is **acyclic**: `app → core-theme + samples`; `samples → core-camera + core-ui`;\n`core-ui → api(core-camera, core-theme)`; `core-camera` and `core-theme` are leaves.\n\n**Naming.** Module `samples/{api}-{feature}` → package `com.android.{api}.{feature}` → composables\n`{Api}{Feature}Screen` / `{Api}{Feature}ViewModel` / `{Api}{Feature}Controller` /\n`{Api}{Feature}UiState`, where `{api}` ∈ `{camerax, camera2}`.\n\n## Build, run, format\n\n- **JDK 17** toolchain (`jvmToolchain(17)`); **compileSdk / targetSdk 37** (Android 17), **minSdk 23**.\n- Build everything: `./gradlew assembleDebug` · Install: `./gradlew :app:installDebug`.\n- **Format (required, CI-gated):** `./gradlew spotlessApply` (auto-fix) / `./gradlew spotlessCheck`.\n  Spotless = ktlint + the Apache header from `spotless/copyright.kt`. `.editorconfig` sets\n  `ktlint_function_naming_ignore_when_annotated_with = Composable` so PascalCase `@Composable` names\n  pass.\n- Key versions (`gradle/libs.versions.toml`): AGP 9.2.1, Kotlin 2.4.0, Gradle 9.6, Compose BOM\n  2026.06.00, Material 3 1.5.0-alpha22, androidx.camera 1.6.1 (`camera-compose`,\n  `camera-viewfinder-compose`), Media3 1.10.1, Hilt 2.59.2, Navigation-Compose 2.9.8,\n  kotlinx-serialization 1.11.0, ML Kit (barcode 17.3, image-labeling 17.0.9, selfie-segmentation\n  16.0.0-beta6), KSP 2.3.5, Spotless 8.7.0.\n\n## Architecture & programming paradigm\n\nLayered, unidirectional (UDF), Compose-first — see [`android_architecture.md`](android_architecture.md).\nEach sample is **four pieces**:\n\n- **`{Feature}UiState`** — a `sealed interface` of states: `Initial`, one or more feature states\n  (e.g. `Previewing`, `Capturing`, `Recording`, `Captured`), and `Error(message)`. Add `Unsupported`\n  when hardware support is conditional.\n- **`{Feature}ViewModel`** — `@HiltViewModel`, `@Inject constructor`. Exposes **exactly one**\n  `val uiState: StateFlow<{Feature}UiState>` (a private `MutableStateFlow` surfaced immutably).\n  **No** `Context`/`Activity`/lifecycle/`Resources`; **no** events pushed to the UI (process the\n  event, update state). Business logic lives here.\n- **`{Feature}Controller`** — a `@Stable` state-holder that owns the **camera SDK lifecycle**\n  (open/close, sessions, capture/record). Created from the Screen via a `remember{Feature}Controller(...)`\n  composable. Camera2 controllers extend `BaseCamera2Controller`; CameraX controllers wrap\n  `ProcessCameraProvider`. The controller is the only place that holds `Context`.\n- **`{Feature}Screen`** — the screen-level composable. Collects with `collectAsStateWithLifecycle()`,\n  wraps content in `CameraSampleScaffold(...)`, and branches with `when (state)`.\n\n**Conventions**\n\n- Lifecycle-aware collection (`collectAsStateWithLifecycle`); never override Activity lifecycle methods\n  — use `LifecycleEventObserver` + `DisposableEffect`.\n- Every reusable composable (not the top-level Screen) takes `modifier: Modifier = Modifier` as its\n  **first optional** parameter.\n- Single-activity app + Navigation-Compose; the `NavHost` is derived from `sampleCatalog`.\n- **Single controller call-site for long-lived state.** When several states all show the camera\n  (`Previewing` / `Recording` / `Captured`), create the controller from **one** call site (e.g. a\n  shared `else ->` branch), not separately per `when` branch — otherwise a state change disposes and\n  recreates the controller and **kills an in-progress recording**. Overlay review screens on top of\n  the live content instead of swapping the whole branch.\n- DI: Hilt — `@HiltAndroidApp` Application, `@AndroidEntryPoint` MainActivity, ViewModels via\n  `hiltViewModel(...)`.\n\n## The viewfinder rule (expanded)\n\n- **CameraX** → render the `SurfaceRequest` through **`CameraXPreview`** (`:core-camera`), which uses\n  `androidx.camera.compose.CameraXViewfinder`.\n- **Camera2** → render through **`Camera2Preview`** (`:core-camera`), which uses\n  `androidx.camera.viewfinder.compose.Viewfinder` driven by a `BaseCamera2Controller`.\n- **Never** instantiate `PreviewView`, `ViewfinderView`, `SurfaceView`, or `TextureView`. Even the\n  captured-video player is Compose (Media3 `PlayerSurface`), not a `VideoView`.\n\n## Strings & resources\n\n- Each sample module owns its `res/values/strings.xml`. **No hardcoded user-facing text** anywhere.\n- Catalog strings live in `app/src/main/res/values/strings.xml` with keys\n  `{api}_{feature}_list_title` and `{api}_{feature}_list_description` (title format e.g.\n  `\"CameraX • Take a Photo\"`). In-screen strings use the sample module's own keys.\n\n## UI / design system — \"Console\"\n\nA dark, pro/technical aesthetic (`:core-theme` + `:core-ui`).\n\n- **Accent** violet `#B59CFF` (`primary`) on `#190F33` (`onPrimary`); dark surfaces; hairline borders.\n- **Type**: `displayFontFamily` / `bodyFontFamily` = Space Grotesk; `monoFontFamily` = Space Mono for\n  all technical/metadata text (API labels, filter chips, counts, HUD readouts).\n- **Home catalog** (`:app` `catalog/ui`): mono header + live count, the `SampleCategory` filter pills\n  (All · Images · Video · ML · Graphics · Extensions · Controls), a bordered featured hero\n  (`CatalogWideCard`), and a 2-column grid of compact tiles (`CatalogRowCard`) grouped under `CAMERAX`\n  / `CAMERA2` section labels. The tile API label is accent for CameraX, muted for Camera2; a category\n  dot uses the first tag's color.\n- **Viewfinder chrome** (`:core-ui`): scrim top-bar buttons (`ScrimIconButton`), centered title chip\n  (`ViewfinderTitleChip`), `RuleOfThirdsGrid`, the accent square `FocusIndicator` reticle, a bottom\n  `CameraControlsBar` with `ShutterButton` / `RecordButton`, `TorchChip` / `TorchGlow`, and\n  `ZoomControls`.\n- **In-app settings** use `SettingsOverlay` (+ `SettingsHeader` / `SettingsRow` / `SettingsDropdown`\n  accent chips / `settingsSwitchColors`) — never raw Material dropdowns.\n- **Captured-media review** via `CapturedImagePreview` / `CapturedVideoPreview`.\n\n## Component reference (reuse these — don't duplicate)\n\n### `:core-theme` (`com.android.camera.coretheme`)\n- **`AISampleCatalogTheme(darkTheme, contrast, content)`** — app theme wrapper (Material 3 +\n  `AppTypography`). `Theme.kt`\n- **Color tokens** (violet `primary`/`onPrimary`, dark surfaces, …) + **`extendedColorScheme`**\n  (`media3`, `mLKit`, `imagen`, `geminiProFlash`, … consumed by `SampleTags`). `Color.kt`, `Theme.kt`\n- **`displayFontFamily`**, **`bodyFontFamily`** (Space Grotesk), **`monoFontFamily`** (Space Mono),\n  **`AppTypography`**. `Type.kt`\n\n### `:core-camera` (`com.android.camera.core.*`)\n- **`BaseCamera2Controller`** — `@Stable` abstract base: background-thread open/close, session creation\n  (`createCaptureSession`), display-rotation transform, tap-to-focus (`focus`),\n  `surfaceRequest`/`transformationInfo` state, `release()`; subclasses override `onCameraOpened` /\n  `configureOutput` / etc. `camera2/BaseCamera2Controller.kt`\n- **`Camera2Preview(controller, modifier, onFocusTap)`** — Compose `Viewfinder` host. `camera2/Camera2Preview.kt`\n- **`CameraXPreview(surfaceRequest, modifier, onTapToFocus, onFocusTap)`** — `CameraXViewfinder` host\n  with coordinate transform. `camerax/CameraXPreview.kt`\n- **`ImageProxy.toBitmap(mirror)`** / **`Image.toBitmap(sensorOrientation, mirror)`** — JPEG → oriented\n  `Bitmap`. `image/ImageUtils.kt`\n- **`MediaStoreSaver`** — `saveBitmap(...)` to DCIM/Camera, `newVideoFile()`, `scanFile(...)`. `media/MediaStoreSaver.kt`\n- **`CameraPermissions`** — `PHOTO`, `VIDEO` permission lists. `permissions/CameraPermissions.kt`\n- **`rememberDisplayRotation(): Int`** — current display rotation; recomposes on config change. `display/DisplayRotation.kt`\n\n### `:core-ui` (`com.android.camera.coreui.*`)\n- **scaffold/** — `CameraSampleScaffold(permissions, api, …, content)` (permission gate + black surface\n  + API badge); `CameraApi` enum (`CAMERA2`, `CAMERAX`).\n- **controls/** — `ShutterButton`, `RecordButton`, `CameraSwitchButton`,\n  `ScrimIconButton(onClick, imageVector, …, size, iconSize)`, `CameraControlsBar(startSlot, center, endSlot)`,\n  `ValueSlider(label, value, …, accentColor)`, `ZoomControls(zoomRatio, valueRange, onValueChange, stops)`.\n- **overlay/** — `FocusIndicator(tapOffset)`, `RuleOfThirdsGrid()`, `ViewfinderTitleChip(text)`,\n  `TorchChip(on, onToggle)`, `TorchGlow(visible)`, `SettingsOverlay(visible, onDismiss, content)`,\n  `SettingsHeader(text)`, `SettingsRow(label, trailing)`,\n  `SettingsDropdown(label, options, selected, onSelected, optionLabel)`, `settingsSwitchColors()`.\n- **preview/** — `CapturedImagePreview(bitmap, onRetake, onDone)`, `CapturedVideoPreview(uri, onDismiss)`.\n- **widget/** — `VideoPlayer(player)` (Compose-first Media3 `PlayerSurface`; tap to play/pause),\n  `HdrWindowColorMode(enabled)` (toggle host-window HDR color mode, API 26+).\n- **state/** — `LoadingView()`, `ErrorView(errorMessage, onRetry)`, `UnsupportedView(message)`.\n\n## Catalog model (`app/.../catalog/domain/SampleCatalog.kt`)\n\n- **`sampleCatalog: List<SampleCatalogItem>`** — the single registry; the `NavHost` and the home grid\n  both derive from it.\n- **`SampleCatalogItem(title, description, route, sampleEntryScreen, type, category, tags, isFeatured, keyArt)`**.\n- **`SampleType`** = `{ CAMERAX, CAMERA2 }`; **`SampleCategory`** =\n  `{ IMAGES, VIDEO, ML, GRAPHICS, EXTENSIONS, CONTROLS }` (drives the home filter); **`SampleTags`** =\n  `{ MEDIA3, ML_KIT, VIDEO, EXTENSIONS, ANALYSIS }` (drives the tile's category dot color).\n\n## Adding a new sample\n\n1. **Generate**:\n   ```bash\n   ./gradlew createSample \\\n     -PsampleName=\"camera2-flash\" \\\n     -PscreenName=\"Camera2FlashScreen\" \\\n     -Ptitle=\"Camera2 • Flash\" \\\n     -Pdesc=\"Toggle the flash with Camera2\" \\\n     -Ptype=\"camera2\"          # camera2 (default) | camerax\n   ```\n   This scaffolds `samples/camera2-flash/` (UiState / ViewModel / Controller / Screen + `build.gradle.kts`\n   + `strings.xml`) and wires `settings.gradle.kts`, `app/build.gradle.kts`, the catalog\n   (`SampleCatalog.kt`), and the catalog strings.\n2. `./gradlew spotlessApply`.\n3. Implement the feature in the generated `Controller` + `Screen`; add in-screen strings to the\n   module's `strings.xml`; set the new `SampleCatalogItem`'s `category` (and `tags` / `isFeatured` if\n   relevant).\n4. Honor every TL;DR rule — reuse `:core-ui` / `:core-camera`, render via `CameraXPreview` /\n   `Camera2Preview`, no loose strings, one feature.\n5. Verify: `./gradlew assembleDebug spotlessCheck`.\n","isInternal":false,"tokens":3530,"sizeBytes":13231},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":201},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/plugin.json","title":"claude-plugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/plugin.json)","tokens":300,"sizeBytes":187},{"name":"mcp.json","path":".cursor/mcp.json","rawUrl":"https://raw.githubusercontent.com/android/camera-samples/HEAD/.cursor/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.cursor/mcp.json)","tokens":300,"sizeBytes":180}],"systemPromptSnippet":"<yakaai_skills repo=\"android/camera-samples\">\n<!-- File: AGENTS.md (Tokens: ~3530 | Category: root-instruction) -->\n# AGENTS.md — Android Camera Samples\n\nGuidance for AI coding agents (and humans) working in this repository. **Read this before making\nchanges.** It describes the rules, architecture, design system, and shared components.\n\n## What this is\n\nA single, **Compose-first** Android app — the **Camera Samples Catalog** — that showcases the\n**Camera2** and **CameraX** APIs through **28 small, self-contained samples**. A home catalog routes to\neach sample; every sample is its own Gradle library module and reuses a shared camera / UI / theme\nlayer, so adding a sample means writing *the feature*, not the boilerplate.\n\n## TL;DR — rules you must not violate\n\n1. **Viewfinder.** CameraX previews use **`CameraXViewfinder`**; Camera2 previews use the Compose\n   **`Viewfinder`**. **Never** use `PreviewView`, `ViewfinderView`, `SurfaceView`, or `TextureView`.\n   In practice always render through the shared `CameraXPreview` / `Camera2Preview` composables\n   (`:core-camera`) — there are **no Android Views** in sample/UI code.\n2. **One feature per sample.** Each sample demonstrates exactly one capability. Don't bundle features\n   unless a feature genuinely requires another (e.g. *Green Screen* = concurrent camera + ML Kit\n   segmentation, *Feature Combination* = several CameraX features by design).\n3. **No loose strings.** Every user-facing string lives in a module's `res/values/strings.xml` and is\n   read via `stringResource(...)` / `context.getString(...)`. No string literals in `Text(...)`,\n   `contentDescription`, button labels, etc.\n4. **Reuse the shared layers** (`:core-camera`, `:core-ui`, `:core-theme`). Don't reinvent shutter\n   buttons, control bars, scaffolds, previews, sliders, overlays, theme tokens, etc.\n5. **Layered architecture** (see below): `UiState` → `ViewModel` → `Controller` → `Screen`.\n   ViewModels never reference `Context`, `Activity`, lifecycle, or `Resources`.\n6. **Guard optional hardware** → render `UnsupportedView` instead of crashing.\n7. **Apache license header** on every file; run **`./gradlew spotlessApply`** (ktlint + headers)\n   before finishing.\n\n## Repository layout / module graph\n\n```\n:app                      home catalog + Navigation-Compose NavHost + Hilt application\n:core-theme               design system (colors, typography, AISampleCatalogTheme)        — leaf\n:core-camera              Camera2/CameraX plumbing (controllers, previews, utils)         — re-exports camera libs\n:core-ui                  shared Compose chrome (scaffold, controls, overlays, review)    — api(:core-camera, :core-theme)\n:samples:{api}-{feature}  one library module per sample                                   — depends on :core-camera + :core-ui\n```\n\nDependency direction is **acyclic**: `app → core-theme + samples`; `samples → core-camera + core-ui`;\n`core-ui → api(core-camera, core-theme)`; `core-camera` and `core-theme` are leaves.\n\n**Naming.** Module `samples/{api}-{feature}` → package `com.android.{api}.{feature}` → composables\n`{Api}{Feature}Screen` / `{Api}{Feature}ViewModel` / `{Api}{Feature}Controller` /\n`{Api}{Feature}UiState`, where `{api}` ∈ `{camerax, camera2}`.\n\n## Build, run, format\n\n- **JDK 17** toolchain (`jvmToolchain(17)`); **compileSdk / targetSdk 37** (Android 17), **minSdk 23**.\n- Build everything: `./gradlew assembleDebug` · Install: `./gradlew :app:installDebug`.\n- **Format (required, CI-gated):** `./gradlew spotlessApply` (auto-fix) / `./gradlew spotlessCheck`.\n  Spotless = ktlint + the Apache header from `spotless/copyright.kt`. `.editorconfig` sets\n  `ktlint_function_naming_ignore_when_annotated_with = Composable` so PascalCase `@Composable` names\n  pass.\n- Key versions (`gradle/libs.versions.toml`): AGP 9.2.1, Kotlin 2.4.0, Gradle 9.6, Compose BOM\n  2026.06.00, Material 3 1.5.0-alpha22, androidx.camera 1.6.1 (`camera-compose`,\n  `camera-viewfinder-compose`), Media3 1.10.1, Hilt 2.59.2, Navigation-Compose 2.9.8,\n  kotlinx-serialization 1.11.0, ML Kit (barcode 17.3, image-labeling 17.0.9, selfie-segmentation\n  16.0.0-beta6), KSP 2.3.5, Spotless 8.7.0.\n\n## Architecture & programming paradigm\n\nLayered, unidirectional (UDF), Compose-first — see [`android_architecture.md`](android_architecture.md).\nEach sample is **four pieces**:\n\n- **`{Feature}UiState`** — a `sealed interface` of states: `Initial`, one or more feature states\n  (e.g. `Previewing`, `Capturing`, `Recording`, `Captured`), and `Error(message)`. Add `Unsupported`\n  when hardware support is conditional.\n- **`{Feature}ViewModel`** — `@HiltViewModel`, `@Inject constructor`. Exposes **exactly one**\n  `val uiState: StateFlow<{Feature}UiState>` (a private `MutableStateFlow` surfaced immutably).\n  **No** `Context`/`Activity`/lifecycle/`Resources`; **no** events pushed to the UI (process the\n  event, update state). Business logic lives here.\n- **`{Feature}Controller`** — a `@Stable` state-holder that owns the **camera SDK lifecycle**\n  (open/close, sessions, capture/record). Created from the Screen via a `remember{Feature}Controller(...)`\n  composable. Camera2 controllers extend `BaseCamera2Controller`; CameraX controllers wrap\n  `ProcessCameraProvider`. The controller is the only place that holds `Context`.\n- **`{Feature}Screen`** — the screen-level composable. Collects with `collectAsStateWithLifecycle()`,\n  wraps content in `CameraSampleScaffold(...)`, and branches with `when (state)`.\n\n**Conventions**\n\n- Lifecycle-aware collection (`collectAsStateWithLifecycle`); never override Activity lifecycle methods\n  — use `LifecycleEventObserver` + `DisposableEffect`.\n- Every reusable composable (not the top-level Screen) takes `modifier: Modifier = Modifier` as its\n  **first optional** parameter.\n- Single-activity app + Navigation-Compose; the `NavHost` is derived from `sampleCatalog`.\n- **Single controller call-site for long-lived state.** When several states all show the camera\n  (`Previewing` / `Recording` / `Captured`), create the controller from **one** call site (e.g. a\n  shared `else ->` branch), not separately per `when` branch — otherwise a state change disposes and\n  recreates the controller and **kills an in-progress recording**. Overlay review screens on top of\n  the live content instead of swapping the whole branch.\n- DI: Hilt — `@HiltAndroidApp` Application, `@AndroidEntryPoint` MainActivity, ViewModels via\n  `hiltViewModel(...)`.\n\n## The viewfinder rule (expanded)\n\n- **CameraX** → render the `SurfaceRequest` through **`CameraXPreview`** (`:core-camera`), which uses\n  `androidx.camera.compose.CameraXViewfinder`.\n- **Camera2** → render through **`Camera2Preview`** (`:core-camera`), which uses\n  `androidx.camera.viewfinder.compose.Viewfinder` driven by a `BaseCamera2Controller`.\n- **Never** instantiate `PreviewView`, `ViewfinderView`, `SurfaceView`, or `TextureView`. Even the\n  captured-video player is Compose (Media3 `PlayerSurface`), not a `VideoView`.\n\n## Strings & resources\n\n- Each sample module owns its `res/values/strings.xml`. **No hardcoded user-facing text** anywhere.\n- Catalog strings live in `app/src/main/res/values/strings.xml` with keys\n  `{api}_{feature}_list_title` and `{api}_{feature}_list_description` (title format e.g.\n  `\"CameraX • Take a Photo\"`). In-screen strings use the sample module's own keys.\n\n## UI / design system — \"Console\"\n\nA dark, pro/technical aesthetic (`:core-theme` + `:core-ui`).\n\n- **Accent** violet `#B59CFF` (`primary`) on `#190F33` (`onPrimary`); dark surfaces; hairline borders.\n- **Type**: `displayFontFamily` / `bodyFontFamily` = Space Grotesk; `monoFontFamily` = Space Mono for\n  all technical/metadata text (API labels, filter chips, counts, HUD readouts).\n- **Home catalog** (`:app` `catalog/ui`): mono header + live count, the `SampleCategory` filter pills\n  (All · Images · Video · ML · Graphics · Extensions · Controls), a bordered featured hero\n  (`CatalogWideCard`), and a 2-column grid of compact tiles (`CatalogRowCard`) grouped under `CAMERAX`\n  / `CAMERA2` section labels. The tile API label is accent for CameraX, muted for Camera2; a category\n  dot uses the first tag's color.\n- **Viewfinder chrome** (`:core-ui`): scrim top-bar buttons (`ScrimIconButton`), centered title chip\n  (`ViewfinderTitleChip`), `RuleOfThirdsGrid`, the accent square `FocusIndicator` reticle, a bottom\n  `CameraControlsBar` with `ShutterButton` / `RecordButton`, `TorchChip` / `TorchGlow`, and\n  `ZoomControls`.\n- **In-app settings** use `SettingsOverlay` (+ `SettingsHeader` / `SettingsRow` / `SettingsDropdown`\n  accent chips / `settingsSwitchColors`) — never raw Material dropdowns.\n- **Captured-media review** via `CapturedImagePreview` / `CapturedVideoPreview`.\n\n## Component reference (reuse these — don't duplicate)\n\n### `:core-theme` (`com.android.camera.coretheme`)\n- **`AISampleCatalogTheme(darkTheme, contrast, content)`** — app theme wrapper (Material 3 +\n  `AppTypography`). `Theme.kt`\n- **Color tokens** (violet `primary`/`onPrimary`, dark surfaces, …) + **`extendedColorScheme`**\n  (`media3`, `mLKit`, `imagen`, `geminiProFlash`, … consumed by `SampleTags`). `Color.kt`, `Theme.kt`\n- **`displayFontFamily`**, **`bodyFontFamily`** (Space Grotesk), **`monoFontFamily`** (Space Mono),\n  **`AppTypography`**. `Type.kt`\n\n### `:core-camera` (`com.android.camera.core.*`)\n- **`BaseCamera2Controller`** — `@Stable` abstract base: background-thread open/close, session creation\n  (`createCaptureSession`), display-rotation transform, tap-to-focus (`focus`),\n  `surfaceRequest`/`transformationInfo` state, `release()`; subclasses override `onCameraOpened` /\n  `configureOutput` / etc. `camera2/BaseCamera2Controller.kt`\n- **`Camera2Preview(controller, modifier, onFocusTap)`** — Compose `Viewfinder` host. `camera2/Camera2Preview.kt`\n- **`CameraXPreview(surfaceRequest, modifier, onTapToFocus, onFocusTap)`** — `CameraXViewfinder` host\n  with coordinate transform. `camerax/CameraXPreview.kt`\n- **`ImageProxy.toBitmap(mirror)`** / **`Image.toBitmap(sensorOrientation, mirror)`** — JPEG → oriented\n  `Bitmap`. `image/ImageUtils.kt`\n- **`MediaStoreSaver`** — `saveBitmap(...)` to DCIM/Camera, `newVideoFile()`, `scanFile(...)`. `media/MediaStoreSaver.kt`\n- **`CameraPermissions`** — `PHOTO`, `VIDEO` permission lists. `permissions/CameraPermissions.kt`\n- **`rememberDisplayRotation(): Int`** — current display rotation; recomposes on config change. `display/DisplayRotation.kt`\n\n### `:core-ui` (`com.android.camera.coreui.*`)\n- **scaffold/** — `CameraSampleScaffold(permissions, api, …, content)` (permission gate + black surface\n  + API badge); `CameraApi` enum (`CAMERA2`, `CAMERAX`).\n- **controls/** — `ShutterButton`, `RecordButton`, `CameraSwitchButton`,\n  `ScrimIconButton(onClick, imageVector, …, size, iconSize)`, `CameraControlsBar(startSlot, center, endSlot)`,\n  `ValueSlider(label, value, …, accentColor)`, `ZoomControls(zoomRatio, valueRange, onValueChange, stops)`.\n- **overlay/** — `FocusIndicator(tapOffset)`, `RuleOfThirdsGrid()`, `ViewfinderTitleChip(text)`,\n  `TorchChip(on, onToggle)`, `TorchGlow(visible)`, `SettingsOverlay(visible, onDismiss, content)`,\n  `SettingsHeader(text)`, `SettingsRow(label, trailing)`,\n  `SettingsDropdown(label, options, selected, onSelected, optionLabel)`, `settingsSwitchColors()`.\n- **preview/** — `CapturedImagePreview(bitmap, onRetake, onDone)`, `CapturedVideoPreview(uri, onDismiss)`.\n- **widget/** — `VideoPlayer(player)` (Compose-first Media3 `PlayerSurface`; tap to play/pause),\n  `HdrWindowColorMode(enabled)` (toggle host-window HDR color mode, API 26+).\n- **state/** — `LoadingView()`, `ErrorView(errorMessage, onRetry)`, `UnsupportedView(message)`.\n\n## Catalog model (`app/.../catalog/domain/SampleCatalog.kt`)\n\n- **`sampleCatalog: List<SampleCatalogItem>`** — the single registry; the `NavHost` and the home grid\n  both derive from it.\n- **`SampleCatalogItem(title, description, route, sampleEntryScreen, type, category, tags, isFeatured, keyArt)`**.\n- **`SampleType`** = `{ CAMERAX, CAMERA2 }`; **`SampleCategory`** =\n  `{ IMAGES, VIDEO, ML, GRAPHICS, EXTENSIONS, CONTROLS }` (drives the home filter); **`SampleTags`** =\n  `{ MEDIA3, ML_KIT, VIDEO, EXTENSIONS, ANALYSIS }` (drives the tile's category dot color).\n\n## Adding a new sample\n\n1. **Generate**:\n   ```bash\n   ./gradlew createSample \\\n     -PsampleName=\"camera2-flash\" \\\n     -PscreenName=\"Camera2FlashScreen\" \\\n     -Ptitle=\"Camera2 • Flash\" \\\n     -Pdesc=\"Toggle the flash with Camera2\" \\\n     -Ptype=\"camera2\"          # camera2 (default) | camerax\n   ```\n   This scaffolds `samples/camera2-flash/` (UiState / ViewModel / Controller / Screen + `build.gradle.kts`\n   + `strings.xml`) and wires `settings.gradle.kts`, `app/build.gradle.kts`, the catalog\n   (`SampleCatalog.kt`), and the catalog strings.\n2. `./gradlew spotlessApply`.\n3. Implement the feature in the generated `Controller` + `Screen`; add in-screen strings to the\n   module's `strings.xml`; set the new `SampleCatalogItem`'s `category` (and `tags` / `isFeatured` if\n   relevant).\n4. Honor every TL;DR rule — reuse `:core-ui` / `:core-camera`, render via `CameraXPreview` /\n   `Camera2Preview`, no loose strings, one feature.\n5. Verify: `./gradlew assembleDebug spotlessCheck`.\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/marketplace.json)\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.claude-plugin/plugin.json)\n\n<!-- File: .cursor/mcp.json (Tokens: ~300 | Category: mcp-config) -->\n# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/android/camera-samples/HEAD/.cursor/mcp.json)\n</yakaai_skills>"}