Skip to content

Camera Feature Policy API - #493

Open
Kimblebee wants to merge 45 commits into
mainfrom
kim/developerOptions/hybrid-api
Open

Kimblebee wants to merge 45 commits into
mainfrom
kim/developerOptions/hybrid-api

Conversation

@Kimblebee

@Kimblebee Kimblebee commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Introduces CameraFeaturePolicy in :core:settings to allow developer options, embedding applications, and test harnesses to configure default camera settings and control UI option visibility.

This decouples the baseline camera session state (defaultValue) from Compose UI presentation (OptionVisibility), enabling controls to be shown normally, hidden entirely, or restricted to a specific whitelist of options with guaranteed domain safety and graceful fallbacks.


Key Changes

  • Configuration & Models (core:settings):

    • Added CameraFeaturePolicy with nullable properties: captureMode, aspectRatio, flashMode, imageFormat, and dynamicRange.
    • Added generic, non-null bounded SettingConfig<T : Any>(defaultValue, visibility).
    • Defined the OptionVisibility<T : Any> sealed hierarchy:
      • OptionVisibility.Visible: All device-supported options are selectable (default).
      • OptionVisibility.Hidden: Setting is hidden/unavailable in the UI; camera runs with defaultValue.
      • OptionVisibility.Only(enabledOptions): Whitelists specific options in the UI (minimum 2 options; supports vararg construction).
      • OptionVisibility.from(options) / OptionVisibility.from(vararg options): Safe factory functions that automatically fall back to Hidden when fewer than 2 options are provided, preventing runtime crash traps during dynamic option resolution.
    • Enforced domain safety constraints:
      • defaultValue must be included in OptionVisibility.Only.
      • flashMode must default to OFF when Hidden and must include OFF when restricted.
      • Ultra HDR (JPEG_ULTRA_HDR) and HDR video (HLG10) are supported as defaults under Hidden (hardware fallbacks to SDR are handled gracefully at runtime if unsupported by the active camera).
    • Added CameraFeaturePolicy.toCameraAppSettings(defaultSettings) to map overrides cleanly over baseline settings.
  • Settings Storage & Data Layer (data:settings, core:settings:datastore-prefs):

    • Updated LocalSettingsRepository and PrefsDataStoreSettingsDataSource to enforce CameraFeaturePolicy:
      • Utilizes enforceRestrictions(storedSetting, settingConfig) across all settings (captureMode, aspectRatio, flashMode, imageFormat, dynamicRange) to preserve valid user-selected preferences under OptionVisibility.Only rather than unconditionally overwriting them with defaultValue.
  • Camera System Wiring (data:camera):

    • Injected CameraFeaturePolicy into CameraXCameraSystemRepository.
    • Applied CameraFeaturePolicy defaults during lazy camera initialization before use cases bind.
    • Preserved un-overridden settings from user preferences rather than hardcoded fallbacks.
  • UI State & Adapters (ui:uistateadapter:capture):

    • Standardized developer restriction parameter naming to optionVisibility across all option adapters.
    • Integrated OptionVisibility policies into:
      • CaptureModeUiStateAdapter: Respects hidden capture mode, hides/disables toggle buttons or quick-settings rows appropriately, and provides clear disabled rationales. Documented the binary quick-toggle contract for CaptureModeToggleUiState (requiring 2 valid selectable states; directing custom layouts to CaptureModeUiState for disabled reasons).
      • FlashModeUiStateAdapter: Filters selectable flash modes or marks as Unavailable when hidden or unsupported.
      • AspectRatioUiStateAdapter: Restricts selectable aspect ratios and falls back deterministically to 3:4 if the currently stored selection is restricted.
      • HdrUiStateAdapter: Disables HDR options when dynamic range or image format are restricted. Fixed an issue where Low Light Boost disabled all HDR options instead of keeping "Off" selectable.
      • CaptureUiStateAdapter: Wires CameraFeaturePolicy into compound UI state generation with cameraFeaturePolicy: CameraFeaturePolicy? = null default parameter.
  • Viewfinder & Quick Settings UI (feature:preview):

    • Verified quickSettingsState.value is QuickSettingsUiState.Available in isQuickSettingsVisible within PreviewScreen.kt so that the drop-down toggle button (ToggleQuickSettingsButton) is hidden whenever quick settings is Unavailable.
  • Dependency Injection & App Integration (app):

    • Provided CameraFeaturePolicy via AppModule.providesCameraFeaturePolicy().
    • Added @Volatile @VisibleForTesting var testCameraFeaturePolicy: CameraFeaturePolicy? to enable isolated, deterministic device test overrides.
  • Testing:

    • Unit Tests:
      • :core:settings: Comprehensive validation of CameraFeaturePolicy, SettingConfig, and OptionVisibility edge cases in CameraFeaturePolicyTest.
      • :data:camera: Verified lazy initialization and default overrides in CameraXCameraSystemRepositoryTest.
      • :data:settings: Validated preference persistence and restriction enforcement in LocalSettingsRepositoryTest.
      • :ui:uistateadapter:capture: Tested UI state adaptation, visibility restrictions, and fallback behavior across CaptureModeUiStateAdapterTest (including concurrent camera dual & HDR conflict scenarios), FlashModeUiStateAdapterTest, AspectRatioUiStateAdapterTest, HdrUiStateAdapterTest, and CaptureUiStateAdapterTest.
      • :feature:preview: Verified ViewModel integration with CameraFeaturePolicy in PreviewViewModelTest.
    • Device Instrumentation Tests (app):
      • Added end-to-end device tests validating Compose UI behavior under various CameraFeaturePolicy configurations:
        • CaptureModeFeaturePolicyDeviceTest (including photo and video capture execution)
        • FlashModeFeaturePolicyDeviceTest (including graceful fallback on devices without flash units)
        • AspectRatioFeaturePolicyDeviceTest
        • HdrFeaturePolicyDeviceTest
      • Isolated CaptureModeSettingsTest.hdr_supports_video_only() using testCameraFeaturePolicy for deterministic behavior across dual-HDR and single-HDR devices.

Usage Example

val policy = CameraFeaturePolicy(
    captureMode = SettingConfig(
        defaultValue = CaptureMode.IMAGE_ONLY,
        visibility = OptionVisibility.Hidden
    ),
    flashMode = SettingConfig(
        defaultValue = FlashMode.OFF,
        visibility = OptionVisibility.Only(FlashMode.OFF, FlashMode.AUTO)
    ),
    dynamicRange = SettingConfig(
        defaultValue = DynamicRange.SDR,
        visibility = OptionVisibility.Hidden
    )
)

Limitations & Future Work

  1. Audio Configuration: audioEnabled configuration is deferred to a follow-up PR, pending visual UX design.
  2. Aspect Ratio Preservation Across Mode Switches: Photo mode currently enforces 3:4 for external capture compatibility. We should consider if we want to preserve developer-configured aspect ratios (e.g., 1:1) for these scenarios.
  3. Quick Settings Navigation Coordination: When all Quick Settings rows are unavailable, coordinate with onNavigateToSettings to determine whether the toggle button should navigate directly to full settings or remain hidden.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new DeveloperAppConfig API to allow overriding default camera settings and restricting UI options, which is integrated into the PreviewViewModel and navigation logic. Several issues were identified in the review: a compilation error in PreviewViewModelTest due to passing null to a non-nullable parameter, inconsistent Java toolchain versions in the new module, and incorrect test tags and string resources in the UI components. Additionally, the OptionRestrictionConfig validation was found to be overly restrictive, and a potential runtime crash was noted in the CaptureModeUiStateAdapter.

Comment thread data/settings/api/build.gradle.kts Outdated
Comment thread ui/components/capture/src/main/res/values/strings.xml Outdated
@Kimblebee
Kimblebee changed the base branch from main to kim/refactor/quickSettings/button-rows July 6, 2026 13:48
@Kimblebee
Kimblebee force-pushed the kim/refactor/quickSettings/button-rows branch from 2370297 to bfd867e Compare July 29, 2026 17:41
@Kimblebee
Kimblebee marked this pull request as ready for review August 14, 2026 18:20
@Kimblebee
Kimblebee force-pushed the kim/refactor/quickSettings/button-rows branch from 5d67110 to 3b7dfd5 Compare August 26, 2026 22:00
Base automatically changed from kim/refactor/quickSettings/button-rows to main August 31, 2026 17:19
@Kimblebee
Kimblebee force-pushed the kim/developerOptions/hybrid-api branch from 5a4bf7f to f977fd1 Compare September 3, 2026 22:29
@Kimblebee
Kimblebee force-pushed the kim/developerOptions/hybrid-api branch from f977fd1 to 1276240 Compare September 4, 2026 03:49
…ityConfig and uiVisibility, add Hidden and UNKNOWN fallbacks

- Rename OptionRestrictionConfig to OptionAvailabilityConfig and uiRestriction to uiVisibility
- Rename FullyRestricted to Hidden and remove artificial invoke() operator on singleton data objects
- Validate that OptionsEnabled contains at least 2 options and that flashMode defaults to OFF when Hidden
- Replace unhandled RuntimeException in CaptureModeUiStateAdapter with DisabledReason.UNKNOWN
- Annotate providesDeveloperAppConfig() with @singleton in AppModule
- Update all unit tests across data:settings:api, feature:preview, and ui:uistateadapter:capture
Comment thread app/src/main/java/com/google/jetpackcamera/MainActivity.kt Outdated
…hybrid-api

# Conflicts:
#	feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewViewModel.kt
#	feature/preview/src/test/java/com/google/jetpackcamera/feature/preview/PreviewViewModelTest.kt
#	ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/compound/CaptureUiStateAdapter.kt
#	ui/uistateadapter/capture/src/test/java/com/google/jetpackcamera/ui/uistateadapter/capture/CaptureUiStateAdapterTest.kt
Remove KEY_USE_DEVELOPER_CONFIG Intent extra and navigation argument plumbing
across MainActivity, JcaApp, and PreviewNavigation. DeveloperAppConfig is now
injected directly via Hilt into PreviewViewModel and applied unconditionally.
Updates PreviewViewModelTest to verify restrictions directly from injected
appConfig.
…make properties nullable

Rename DeveloperAppConfig to CameraAppConfig (matching Jetpack/CameraX conventions)
and update property names (imageFormat, dynamicRange). Make all properties
nullable with a null default in CameraAppConfig, ensuring toCameraAppSettings()
preserves persistent user preferences when individual settings are unconstrained.
Update AppModule, PreviewViewModel, CaptureUiStateAdapter, and associated unit tests.
… and update E2E tests

- Rename restrictionConfig to visibilityConfig and make it nullable with null default
  in CaptureModeUiStateAdapter.
- Guard CaptureModeUiState.from against unselectable active capture mode, returning
  Unavailable to prevent IllegalStateException.
- Update CaptureModeSettingsTest to verify that the capture mode toggle switch is
  removed (assertDoesNotExist) rather than disabled when switching is not supported.
- Rename providesDeveloperAppConfig to providesCameraAppConfig in AppModule.
…nd remove :data:settings:api

Move CameraAppConfig and its unit tests into :core:settings under the
com.google.jetpackcamera.settings.model package, co-located with
CameraAppSettings and Constraints.

Remove the obsolete :data:settings:api module and its references from
settings.gradle.kts, app, feature:preview, and ui:uistateadapter:capture.
This resolves the architectural layering inversion where UI state adapters
depended on a data-layer module.
…ystemRepository

- Inject CameraAppConfig into CameraXCameraSystemRepository via CameraModule.
- Apply cameraAppConfig.toCameraAppSettings(...) to initial settings during lazy camera startup.
- Add testCameraAppConfig test override hook in AppModule for instrumentation tests.
- Fix missing composeTestRule prefix in CaptureModeSettingsTest.
@Kimblebee Kimblebee changed the title developer options api Settings Config API Sep 15, 2026
…nts when hidden

- Require ImageOutputFormat.JPEG when imageFormat is Hidden or OptionsEnabled.
- Require DynamicRange.SDR when dynamicRange is Hidden or OptionsEnabled.
- Add unit tests verifying these invariants in CameraAppConfigTest.
…tory

- Add getCameraSystem_withCameraAppConfig_appliesDefaultValues() verifying
  startup initialization passes overridden default settings to CameraSystem.
- Add FlashModeAppConfigDeviceTest parameterized across BACK and FRONT lenses.
- Add CaptureModeAppConfigDeviceTest verifying default, hidden, and filtered capture modes.
- Add HdrAppConfigDeviceTest parameterized across BACK and FRONT lenses for image and video HDR.
- Add AspectRatioAppConfigDeviceTest verifying aspect ratio defaults in Quick Settings.
- Rename OptionAvailabilityConfig to OptionVisibility.
- Rename OptionAvailabilityConfig.NotRestricted to OptionVisibility.Visible.
- Rename OptionAvailabilityConfig.OptionsEnabled to OptionVisibility.Only.
- Rename SettingConfig.uiVisibility to SettingConfig.visibility.
- Update all usage across UI state adapters, viewmodels, and tests.
@Kimblebee
Kimblebee requested a review from temcguir September 16, 2026 17:45
@Kimblebee Kimblebee changed the title Settings Config API Camera Feature Policy API Sep 16, 2026
- Add OptionVisibility.from(options) to safely fall back to Hidden
  when fewer than 2 options are provided, preventing runtime exceptions.
- Add unit test coverage in CameraFeaturePolicyTest.
Moves developer baseline defaults and restriction policies into the
settings layer so that unconfigured user preferences from DataStore
are preserved instead of wiped by ad-hoc ViewModel / Camera reconciliation.

- PrefsDataStoreSettingsDataSource: falls back to CameraFeaturePolicy
  baseline defaults for unconfigured preferences.
- LocalSettingsRepository: intercepts defaultCameraAppSettings flow to
  continuously enforce OptionVisibility restrictions (Hidden/Only).
- CameraXCameraSystemRepository: removes ad-hoc toCameraAppSettings()
  reconciliation and initializes directly from settingsRepository.
- Adds comprehensive unit tests in LocalSettingsRepositoryTest and
  verifies DataStore behavior in instrumented tests.
…rols

Connects OptionVisibility restrictions for AspectRatio from CameraFeaturePolicy
into AspectRatioUiState and QuickSettings:
- AspectRatioUiStateAdapter: accepts an optional visibilityConfig parameter.
  When set to OptionVisibility.Hidden or when device/policy restricts available
  aspect ratios to <= 1 option, returns AspectRatioUiState.Unavailable so that
  the Aspect Ratio row is hidden from Quick Settings. When restricted by
  OptionVisibility.Only, limits available options and falls back to a valid
  selection.
- CaptureUiStateAdapter: wires appConfig.aspectRatio.visibility into
  quickSettingsUiState's AspectRatioUiState, while ensuring previewDisplayUiState
  continues to receive an Available AspectRatioUiState with the selected aspect
  ratio so that the viewfinder surface renders properly.
- AppModule: cleans up manual testing overrides and defaults to clean CameraFeaturePolicy().
- AspectRatioAppConfigDeviceTest: adds Section B (Hidden) and Section C
  (OptionsEnabled / Filtering) E2E device tests verifying that the Aspect Ratio
  quick settings row is absent when hidden and constrained when restricted.
- AspectRatioUiStateAdapterTest & CaptureUiStateAdapterTest: adds unit tests
  verifying UI state adapter emission for hidden, only, and default visibilities.
Clean up remaining vestigial references to CameraAppConfig and
appConfig across the codebase:
- Rename parameters and arguments in CaptureUiStateAdapter and
  PreviewViewModel from appConfig to cameraFeaturePolicy.
- Update unit tests in CaptureUiStateAdapterTest, PreviewViewModelTest,
  CameraFeaturePolicyTest, and CameraXCameraSystemRepositoryTest.
- Move androidTest package from com.google.jetpackcamera.appconfig to
  com.google.jetpackcamera.featurepolicy and rename device test classes
  to *FeaturePolicyDeviceTest.
Add notes on:
- QuickSettingsUiState.Companion.from: returning QuickSettingsUiState.Unavailable
  when all option rows (aspectRatio, captureMode, flashMode, hdr) are Unavailable.
- PreviewScreen: checking quickSettingsState.value is Available in isQuickSettingsVisible
  to hide the quick settings toggle button when all rows are unavailable.
- Drop Hidden checks in CameraFeaturePolicy requiring JPEG or SDR defaults, enabling client apps to default/lock to Ultra HDR and HLG10 video.
- Update CameraFeaturePolicyTest to verify Hidden Ultra HDR and HLG10 success.
- Enforce capture mode restrictions in LocalSettingsRepository to preserve valid selections under OptionVisibility.Only.
- Rename visibilityConfig to optionVisibility in CaptureModeToggleUiState.Companion.from and internal helpers.
- Document binary quick-toggle contract in CaptureModeToggleUiState KDoc.
- Remove unreachable OptionVisibility.Hidden checks from getCaptureModeDisabledReason.
- Update QuickSettingsUiStateAdapter and PreviewScreen TODOs regarding follow-up settings navigation.
- Clean up unused legacy test tags in TestTags.kt.
… KDocs and expand test coverage

- Add prominent Safety Notice to OptionVisibility.Only advising callers to prefer OptionVisibility.from for dynamically resolved option sets.
- Document secondary vararg constructor of OptionVisibility.Only and add cross-referencing @see links.
- Update CameraFeaturePolicy example to demonstrate OptionVisibility.from.
- Add unit tests in CaptureModeUiStateAdapterTest covering concurrent camera DUAL and HDR conflict disabled reasons.
- Assert strict deterministic fallback order in AspectRatioUiStateAdapterTest.
…gs when unavailable and align optionVisibility params

- Check quickSettingsState.value is QuickSettingsUiState.Available in isQuickSettingsVisible to hide quick settings toggle button when unavailable.
- Align remaining visibilityConfig parameters to optionVisibility across AspectRatioUiStateAdapter, FlashModeUiStateAdapter, HdrUiStateAdapter, and CaptureUiStateAdapter.
- Update FlashModeUiStateAdapterTest and HdrUiStateAdapterTest to match new parameter names.
@Kimblebee
Kimblebee force-pushed the kim/developerOptions/hybrid-api branch from 754f643 to 76e2105 Compare September 22, 2026 00:16
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.

2 participants