Repository navigation
Conversation
…mode - Widen 11 session helper declarations in :core:camera with @InternalCameraApi so custom session runners can execute outside :core:camera. - Replace PerpetualSessionSettings.ConcurrentCamera with generic CustomSession dispatched through CameraSessionBinding.Custom. - Implement ConcurrentCameraSubModeProvider as a VIDEO_ONLY capture sub-mode with a declarative CameraFeaturePolicy contributed via @IntoSet in ConcurrentCameraSubModeModule. - Remove hardcoded ConcurrentCameraMode branches from CameraXCameraSystem, :feature:settings, and :ui:uistateadapter:capture. - Remove the concurrent camera setting completely: delete the ConcurrentCameraMode model and proto, reserve its settings proto field, and drop it from the settings data sources, repositories, settings screen, and strings. Concurrent camera is now selected only through the capture mode carousel. - Selecting a capture sub-mode from STANDARD adopts the sub-mode's parent capture mode, so that, e.g., dual camera can be selected without first switching to video. - Update instrumented tests to select concurrent camera through the carousel, and determine the current capture mode from the image/video toggle when a sub-mode replaces the quick settings title.
…rent-camera-submode # Conflicts: # core/camera/src/androidTest/java/com/google/jetpackcamera/core/camera/CameraXCameraSystemTest.kt
…ode-selector # Conflicts: # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/ConcurrentCameraSession.kt # core/camera/testing/src/main/java/com/google/jetpackcamera/core/camera/testing/FakeCameraSystem.kt # ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureLayout.kt
…rent-camera-submode # Conflicts: # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/ConcurrentCameraSession.kt
…rent-camera-submode # Conflicts: # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSystem.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/ConcurrentCameraSession.kt # core/camera/testing/src/main/java/com/google/jetpackcamera/core/camera/testing/FakeCameraSystem.kt
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode # Conflicts: # app/src/androidTest/java/com/google/jetpackcamera/CaptureModeSettingsTest.kt
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode # Conflicts: # core/camera/src/androidTest/java/com/google/jetpackcamera/core/camera/CameraXCameraSystemTest.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSessionSettings.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt
…rent-camera-submode
…rent-camera-submode
…rent-camera-submode
There was a problem hiding this comment.
Code Review
This pull request refactors the concurrent camera mode from a top-level setting into a modular capture sub-mode (concurrent_dual), introducing the :core:camera:concurrent module and updating CameraXCameraSystem to support custom sessions. The feedback suggests simplifying the camera info lookup in ConcurrentCameraSession using Kotlin standard library functions, extracting literal scales and offsets into named constants to avoid magic numbers, and using require with type checks in ConcurrentCameraSubModeProvider to leverage Kotlin's smart casting.
…oncurrent-camera-submode
…oncurrent-camera-submode # Conflicts: # core/camera/src/androidTest/java/com/google/jetpackcamera/core/camera/CameraXCameraSystemTest.kt
A missing concurrent camera pair, a failed bind, or a stream configuration error while Dual is active now reports a capture sub-mode error instead of a camera error, so the camera system switches back to a single camera and the preview shows the sub-mode error snackbar. A removed camera is still reported as a camera error.
…oncurrent-camera-submode
…oncurrent-camera-submode
…oncurrent-camera-submode
…oncurrent-camera-submode
- The Dual policy locks the video quality to the unspecified quality that the concurrent session records with, and restores it on exit. - Add an app test that selects the single-stream effect, records in Dual with the effect turned off, and checks the effect is restored after leaving Dual. - Generalize the sub-mode selection test helper so other sub-modes can use it.
…oncurrent-camera-submode
…ttings' into temcguir/concurrent-camera-submode
…ttings' into temcguir/concurrent-camera-submode
…oncurrent-camera-submode
…oncurrent-camera-submode # Conflicts: # feature/settings/src/main/res/values/strings.xml # ui/uistateadapter/capture/src/main/java/com/google/jetpackcamera/ui/uistateadapter/capture/DisabledReason.kt # ui/uistateadapter/capture/src/main/res/values/strings.xml
…oncurrent-camera-submode # Conflicts: # core/settings/datastore-prefs/src/main/java/com/google/jetpackcamera/core/settings/datastoreprefs/PreferenceKeys.kt # core/settings/datastore-prefs/src/main/java/com/google/jetpackcamera/core/settings/datastoreprefs/PrefsDataStoreSettingsDataSource.kt # feature/settings/src/test/java/com/google/jetpackcamera/settings/CameraAppSettingsViewModelTest.kt
…oncurrent-camera-submode
| old?.copy( | ||
| cameraLensFacing = lensFacing, | ||
| activeCaptureSubModeId = CaptureSubModeId.DEFAULT |
There was a problem hiding this comment.
Resetting activeCaptureSubModeId = CaptureSubModeId.DEFAULT in setLensFacing makes isContinuingSameProvider (activeCaptureSubModeId == requestedSubModeId) always false on lens flips. When flipping BACK -> FRONT in Dual mode, tryApplyCaptureSubModeConstraints() calls restoreCaptureSubModeOverrides() against FRONT constraints and recreates PolicyOverrides, permanently wiping out rear-only pre-sub-mode settings (UHD, HLG10, 60 fps, OPTICAL) and requested = true flags.
Since restoreCaptureSubModeOverrides() already resets activeCaptureSubModeId = DEFAULT whenever a sub-mode isn't supported on the new lens, we can keep activeCaptureSubModeId intact here:
old?.copy(
cameraLensFacing = lensFacing
)| .collectLatest { (primaryState, secondaryState) -> | ||
| val mappedError = (primaryState.error ?: secondaryState.error) | ||
| ?.let { mapCameraStateError(it) } | ||
| if (mappedError == CameraError.StreamConfigError) { | ||
| reportSubModeFailure( | ||
| sessionSettings, | ||
| IllegalStateException( | ||
| "Concurrent stream configuration failed" | ||
| ) | ||
| ) | ||
| return@collectLatest | ||
| } | ||
| val bothOpen = primaryState.type == CXCameraState.Type.OPEN && | ||
| secondaryState.type == CXCameraState.Type.OPEN | ||
| currentCameraState.update { old -> | ||
| old.copy( | ||
| isCameraRunning = | ||
| primaryState.type == CXCameraState.Type.OPEN, | ||
| cameraError = when { | ||
| mappedError != null -> mappedError | ||
| bothOpen -> null | ||
| else -> old.cameraError | ||
| } | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Two issues in this state collector:
- On
ERROR_STREAM_CONFIG,combine(primaryState, secondaryState)emits acrossOPENING -> CLOSING -> CLOSED. Becausereturn@collectLatestonly exits the current emission and each newIllegalStateExceptionuses reference equality, this may emit multiple distinctCaptureSubModeState.Errorstates and queues duplicate snackbars. Guarding withif (currentCameraState.value.captureSubModeState !is CaptureSubModeState.Error)(like inCameraSession.ktline 374) prevents duplicates. - When
bothOpenistrue, this should also resetcaptureSubModeState = CaptureSubModeState.Idle(matchingCameraSession.ktline 389).
…oncurrent-camera-submode
…oncurrent-camera-submode
…oncurrent-camera-submode # Conflicts: # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSession.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraSessionSettings.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/CameraXCameraSystem.kt # core/camera/src/main/java/com/google/jetpackcamera/core/camera/ConcurrentCameraSession.kt # core/model-proto/src/test/java/com/google/jetpackcamera/model/proto/ModelMappersTest.kt
…oncurrent-camera-submode
…oncurrent-camera-submode
Part of the capture sub-mode stack: #598 ← #599 ← #607 ← this PR ← #609.
Architecture
Amber hexagon: changed in this PR. Blue box: unchanged. Purple: the
CaptureSubModeProviderinterface. Dashed arrow: implements. A module labeled with a PR number is added in that PR.Summary
Concurrent camera becomes a capture sub-mode provided by a new optional module,
:core:camera:concurrent(with DI inconcurrent-di). Apps that don't include the module don't depend on concurrent camera code.Changes
ConcurrentCameraSubModeProvidersupplies the "Dual" sub-mode under video capture.CameraSessionBinding.Custom.CameraXCameraSystemdispatchesCameraSessionBinding.Customgenerically. The concurrent session rebinds when the primary lens changes. A missing camera pair, a failed bind, or a stream configuration error reports a sub-mode error, so the camera switches back to a single camera and the preview shows an error snackbar. A removed camera is still reported as a camera error.@InternalCameraApiand require opt-in. Public helpers areCameraSessionContextextensions (applyDeviceRotation,createVideoUseCase,attachPreviewSurfaceProvider,mapCameraStateError), so no public extensions are declared on CameraX types. The CameraX state error mapping staysinternal.Breaking changes (library API)
ConcurrentCameraMode, its mappers, and its proto enum. The proto field number and name are reserved.CameraAppSettings.concurrentCameraModeCameraSystem.setConcurrentCameraModeSettingsRepository.updateConcurrentCameraModeandSettingsDataSource.updateConcurrentCameraModeCameraSystemConstraints.concurrentCamerasSupported:core:camera:concurrentandconcurrent-di, then select the Dual sub-mode.Testing
ConcurrentCameraSubModeProviderTest(including the video quality lock), settings and UI state adapter tests,CameraErrorMappingTest,ModelMappersTest.ConcurrentCameraSessionTest, including the sub-mode error reported when no camera pair is available or binding fails.ConcurrentCameraTest, which skips on devices without concurrent cameras. It covers lens flip keeping Dual selected, the Settings rationale shown during Dual and cleared after, and recording in Dual with the single-stream effect selected: the effect is turned off during Dual and restored after.CaptureModeSettingsTest.