From 964811eb40d8649b54b7a1e178b500d3d7a27d6c Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 12 Feb 2024 16:56:14 -0500 Subject: [PATCH 1/9] Add impl for setExposureMode --- .../lib/src/android_camera_camerax.dart | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index b05788d68a4..9b760b23bb8 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -12,12 +12,14 @@ import 'package:stream_transform/stream_transform.dart'; import 'analyzer.dart'; import 'camera.dart'; +import 'camera2_camera_control.dart'; import 'camera_control.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; import 'camera_state.dart'; import 'camerax_library.g.dart'; import 'camerax_proxy.dart'; +import 'capture_request_options.dart'; import 'device_orientation_manager.dart'; import 'exposure_state.dart'; import 'fallback_strategy.dart'; @@ -461,6 +463,35 @@ class AndroidCameraCameraX extends CameraPlatform { return exposureState.exposureCompensationStep; } + // TODO(camsim99): Move around methods to match camera_platform.dart before landing. + /// Sets the focus mode for taking pictures. + /// + /// [cameraId] is not used. + @override + Future setFocusMode(int cameraId, FocusMode mode) { + throw UnimplementedError('setFocusMode() is not implemented.'); + } + + /// Sets the exposure mode for taking pictures. + /// + /// [cameraId] is not used + @override + Future setExposureMode(int cameraId, ExposureMode mode) async { + // TODO(camsim99): In a previous PR, this will be made a field, so use that instead once it lands. + final CameraControl cameraControl = await camera!.getCameraControl(); + final Camera2CameraControl camera2Control = + Camera2CameraControl(cameraControl: cameraControl); + final bool lockExposureMode = mode == ExposureMode.locked; + + final CaptureRequestOptions captureRequestOptions = CaptureRequestOptions( + requestedOptions: <( + CaptureRequestKeySupportedType, + Object? + )>[(CaptureRequestKeySupportedType.controlAeLock, lockExposureMode)]); + + await camera2Control.addCaptureRequestOptions(captureRequestOptions); + } + /// Gets the maximum supported zoom level for the selected camera. /// /// [cameraId] not used. From 3aa73edb0813e6821f0e183741a7d6d528f8e2ea Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 12 Feb 2024 18:25:21 -0500 Subject: [PATCH 2/9] Add todo for setfocusmode so I can push --- .../camera_android_camerax/lib/src/android_camera_camerax.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 9b760b23bb8..5eeeb32bc6b 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -469,7 +469,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// [cameraId] is not used. @override Future setFocusMode(int cameraId, FocusMode mode) { - throw UnimplementedError('setFocusMode() is not implemented.'); + // TODO(camsim99): Clarify implementation. } /// Sets the exposure mode for taking pictures. From bbf40c58ce3c0c371f2f45a565a3715ba3039228 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 14 Feb 2024 14:55:20 -0500 Subject: [PATCH 3/9] Implement setExposureMode in example app --- .../camera/camera_android_camerax/CHANGELOG.md | 4 ++++ .../camera/camera_android_camerax/README.md | 4 ---- .../camerax/CameraAndroidCameraxPlugin.java | 7 +++++++ .../CaptureRequestOptionsHostApiImpl.java | 3 ++- .../example/lib/main.dart | 17 +++++++++++------ .../lib/src/android_camera_camerax.dart | 9 --------- .../camera/camera_android_camerax/pubspec.yaml | 2 +- 7 files changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index a96bfcefd99..826af5f21f9 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.0+35 + +* Implements `setExposureMode`. + ## 0.5.0+32 * Removes all remaining `unawaited` calls to fix potential race conditions and updates the diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index 3a2e49d7e60..c1dbd27baf9 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -35,10 +35,6 @@ and thus, the plugin will fall back to 480p if configured with a `setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented. -### Focus mode & point configuration \[[Issue #120467][120467]\] - -`setFocusMode` & `setFocusPoint` are unimplemented. - ### Setting maximum duration and stream options for video capture Calling `startVideoCapturing` with `VideoCaptureOptions` configured with diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index b0e0493cbe2..a05496ee8d4 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -27,6 +27,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity @VisibleForTesting @Nullable public ImageCaptureHostApiImpl imageCaptureHostApiImpl; @VisibleForTesting @Nullable public CameraControlHostApiImpl cameraControlHostApiImpl; @VisibleForTesting @Nullable public SystemServicesHostApiImpl systemServicesHostApiImpl; + @VisibleForTesting @Nullable public Camera2CameraControlHostApiImpl camera2CameraControlHostApiImpl; @VisibleForTesting public @Nullable DeviceOrientationManagerHostApiImpl deviceOrientationManagerHostApiImpl; @@ -119,6 +120,9 @@ public void setUp( cameraControlHostApiImpl = new CameraControlHostApiImpl(binaryMessenger, instanceManager, context); GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl); + camera2CameraControlHostApiImpl = new Camera2CameraControlHostApiImpl(instanceManager, context); + GeneratedCameraXLibrary.Camera2CameraControlHostApi.setup(binaryMessenger, camera2CameraControlHostApiImpl); + GeneratedCameraXLibrary.CaptureRequestOptionsHostApi.setup(binaryMessenger, new CaptureRequestOptionsHostApiImpl(instanceManager)); } @Override @@ -210,6 +214,9 @@ public void updateContext(@NonNull Context context) { if (cameraControlHostApiImpl != null) { cameraControlHostApiImpl.setContext(context); } + if (camera2CameraControlHostApiImpl != null) { + camera2CameraControlHostApiImpl.setContext(context); + } } /** Sets {@code LifecycleOwner} that is used to control the lifecycle of the camera by CameraX. */ diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java index 6f8d8c91dda..634abea3380 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java @@ -110,8 +110,9 @@ public void create(@NonNull Long identifier, @NonNull Map options) Map decodedOptions = new HashMap(); for (Map.Entry option : options.entrySet()) { + Integer index = ((Number) option.getKey()).intValue(); decodedOptions.put( - CaptureRequestKeySupportedType.values()[option.getKey().intValue()], option.getValue()); + CaptureRequestKeySupportedType.values()[index], option.getValue()); } instanceManager.addDartCreatedInstance(proxy.create(decodedOptions), identifier); } diff --git a/packages/camera/camera_android_camerax/example/lib/main.dart b/packages/camera/camera_android_camerax/example/lib/main.dart index 4484c5dac98..41f6f05fd29 100644 --- a/packages/camera/camera_android_camerax/example/lib/main.dart +++ b/packages/camera/camera_android_camerax/example/lib/main.dart @@ -282,8 +282,9 @@ class _CameraExampleHomeState extends State IconButton( icon: const Icon(Icons.exposure), color: Colors.blue, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? onExposureModeButtonPressed + : null, ), IconButton( icon: const Icon(Icons.filter_center_focus), @@ -392,16 +393,20 @@ class _CameraExampleHomeState extends State children: [ TextButton( style: styleAuto, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.auto) + : null, onLongPress: () {}, // TODO(camsim99): Add functionality back here., child: const Text('AUTO'), ), TextButton( style: styleLocked, - onPressed: - () {}, // TODO(camsim99): Add functionality back here. + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.locked) + : null, child: const Text('LOCKED'), ), TextButton( diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 5eeeb32bc6b..07729138bb4 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -463,15 +463,6 @@ class AndroidCameraCameraX extends CameraPlatform { return exposureState.exposureCompensationStep; } - // TODO(camsim99): Move around methods to match camera_platform.dart before landing. - /// Sets the focus mode for taking pictures. - /// - /// [cameraId] is not used. - @override - Future setFocusMode(int cameraId, FocusMode mode) { - // TODO(camsim99): Clarify implementation. - } - /// Sets the exposure mode for taking pictures. /// /// [cameraId] is not used diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index c9da1786693..ecc1a4cdde8 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.5.0+32 +version: 0.5.0+35 environment: sdk: ">=3.0.0 <4.0.0" From ab46d0bb46f238b14af8f7594b52ca076ca8088f Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 14 Feb 2024 15:29:23 -0500 Subject: [PATCH 4/9] Add tests --- .../camerax/CameraAndroidCameraxPlugin.java | 12 ++-- .../CaptureRequestOptionsHostApiImpl.java | 3 +- .../CameraAndroidCameraxPluginTest.java | 12 ++++ .../lib/src/android_camera_camerax.dart | 8 +-- .../lib/src/camerax_proxy.dart | 24 +++++++ .../test/android_camera_camerax_test.dart | 58 +++++++++++++++++ .../android_camera_camerax_test.mocks.dart | 35 +++++++++++ .../camera2_camera_control_test.mocks.dart | 63 ++++++++++++------- .../focus_metering_action_test.mocks.dart | 2 +- .../focus_metering_result_test.mocks.dart | 2 +- .../test/metering_point_test.mocks.dart | 2 +- 11 files changed, 187 insertions(+), 34 deletions(-) diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index a05496ee8d4..77f672067d5 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -27,7 +27,9 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity @VisibleForTesting @Nullable public ImageCaptureHostApiImpl imageCaptureHostApiImpl; @VisibleForTesting @Nullable public CameraControlHostApiImpl cameraControlHostApiImpl; @VisibleForTesting @Nullable public SystemServicesHostApiImpl systemServicesHostApiImpl; - @VisibleForTesting @Nullable public Camera2CameraControlHostApiImpl camera2CameraControlHostApiImpl; + + @VisibleForTesting @Nullable + public Camera2CameraControlHostApiImpl camera2CameraControlHostApiImpl; @VisibleForTesting public @Nullable DeviceOrientationManagerHostApiImpl deviceOrientationManagerHostApiImpl; @@ -121,8 +123,10 @@ public void setUp( new CameraControlHostApiImpl(binaryMessenger, instanceManager, context); GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl); camera2CameraControlHostApiImpl = new Camera2CameraControlHostApiImpl(instanceManager, context); - GeneratedCameraXLibrary.Camera2CameraControlHostApi.setup(binaryMessenger, camera2CameraControlHostApiImpl); - GeneratedCameraXLibrary.CaptureRequestOptionsHostApi.setup(binaryMessenger, new CaptureRequestOptionsHostApiImpl(instanceManager)); + GeneratedCameraXLibrary.Camera2CameraControlHostApi.setup( + binaryMessenger, camera2CameraControlHostApiImpl); + GeneratedCameraXLibrary.CaptureRequestOptionsHostApi.setup( + binaryMessenger, new CaptureRequestOptionsHostApiImpl(instanceManager)); } @Override @@ -214,7 +218,7 @@ public void updateContext(@NonNull Context context) { if (cameraControlHostApiImpl != null) { cameraControlHostApiImpl.setContext(context); } - if (camera2CameraControlHostApiImpl != null) { + if (camera2CameraControlHostApiImpl != null) { camera2CameraControlHostApiImpl.setContext(context); } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java index 634abea3380..f76dd5422e7 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java @@ -111,8 +111,7 @@ public void create(@NonNull Long identifier, @NonNull Map options) new HashMap(); for (Map.Entry option : options.entrySet()) { Integer index = ((Number) option.getKey()).intValue(); - decodedOptions.put( - CaptureRequestKeySupportedType.values()[index], option.getValue()); + decodedOptions.put(CaptureRequestKeySupportedType.values()[index], option.getValue()); } instanceManager.addDartCreatedInstance(proxy.create(decodedOptions), identifier); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java index 8c9daf8e014..d9bed4ce358 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java @@ -161,6 +161,8 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { mock(ImageAnalysisHostApiImpl.class); final CameraControlHostApiImpl mockCameraControlHostApiImpl = mock(CameraControlHostApiImpl.class); + final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl = + mock(Camera2CameraControlHostApiImpl.class); when(flutterPluginBinding.getApplicationContext()).thenReturn(mockContext); @@ -172,6 +174,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl; plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl; plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class); + plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onDetachedFromActivityForConfigChanges(); @@ -183,6 +186,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() { verify(mockImageCaptureHostApiImpl).setContext(mockContext); verify(mockImageAnalysisHostApiImpl).setContext(mockContext); verify(mockCameraControlHostApiImpl).setContext(mockContext); + verify(mockCamera2CameraControlHostApiImpl).setContext(mockContext); } @Test @@ -251,6 +255,8 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg mock(CameraControlHostApiImpl.class); final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl = mock(DeviceOrientationManagerHostApiImpl.class); + final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl = + mock(Camera2CameraControlHostApiImpl.class); final ArgumentCaptor permissionsRegistryCaptor = ArgumentCaptor.forClass(PermissionsRegistry.class); @@ -266,6 +272,7 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl; plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl; plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class); + plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onReattachedToActivityForConfigChanges(activityPluginBinding); @@ -282,6 +289,7 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg verify(mockImageCaptureHostApiImpl).setContext(mockActivity); verify(mockImageAnalysisHostApiImpl).setContext(mockActivity); verify(mockCameraControlHostApiImpl).setContext(mockActivity); + verify(mockCamera2CameraControlHostApiImpl).setContext(mockActivity); // Check permissions registry reference is set. verify(mockSystemServicesHostApiImpl) @@ -331,6 +339,8 @@ public void onDetachedFromActivity_setsContextReferencesBasedOnFlutterPluginBind final ImageCaptureHostApiImpl mockImageCaptureHostApiImpl = mock(ImageCaptureHostApiImpl.class); final CameraControlHostApiImpl mockCameraControlHostApiImpl = mock(CameraControlHostApiImpl.class); + final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl = + mock(Camera2CameraControlHostApiImpl.class); final ArgumentCaptor permissionsRegistryCaptor = ArgumentCaptor.forClass(PermissionsRegistry.class); @@ -344,6 +354,7 @@ public void onDetachedFromActivity_setsContextReferencesBasedOnFlutterPluginBind plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl; plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl; plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class); + plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl; plugin.onAttachedToEngine(flutterPluginBinding); plugin.onDetachedFromActivity(); @@ -355,5 +366,6 @@ public void onDetachedFromActivity_setsContextReferencesBasedOnFlutterPluginBind verify(mockImageCaptureHostApiImpl).setContext(mockContext); verify(mockImageAnalysisHostApiImpl).setContext(mockContext); verify(mockCameraControlHostApiImpl).setContext(mockContext); + verify(mockCamera2CameraControlHostApiImpl).setContext(mockContext); } } diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 07729138bb4..12925ca1a3f 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -468,14 +468,14 @@ class AndroidCameraCameraX extends CameraPlatform { /// [cameraId] is not used @override Future setExposureMode(int cameraId, ExposureMode mode) async { - // TODO(camsim99): In a previous PR, this will be made a field, so use that instead once it lands. + // TODO(camsim99): In a previous PR, this will be made a class field, so use that instead once it lands. final CameraControl cameraControl = await camera!.getCameraControl(); final Camera2CameraControl camera2Control = - Camera2CameraControl(cameraControl: cameraControl); + proxy.getCamera2CameraControl(cameraControl); final bool lockExposureMode = mode == ExposureMode.locked; - final CaptureRequestOptions captureRequestOptions = CaptureRequestOptions( - requestedOptions: <( + final CaptureRequestOptions captureRequestOptions = proxy + .createCaptureRequestOptions(<( CaptureRequestKeySupportedType, Object? )>[(CaptureRequestKeySupportedType.controlAeLock, lockExposureMode)]); diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart index 87d3680ff2c..8978ab49639 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart @@ -5,9 +5,12 @@ import 'dart:ui' show Size; import 'analyzer.dart'; +import 'camera2_camera_control.dart'; +import 'camera_control.dart'; import 'camera_selector.dart'; import 'camera_state.dart'; import 'camerax_library.g.dart'; +import 'capture_request_options.dart'; import 'device_orientation_manager.dart'; import 'fallback_strategy.dart'; import 'image_analysis.dart'; @@ -49,6 +52,8 @@ class CameraXProxy { _startListeningForDeviceOrientationChange, this.setPreviewSurfaceProvider = _setPreviewSurfaceProvider, this.getDefaultDisplayRotation = _getDefaultDisplayRotation, + this.getCamera2CameraControl = _getCamera2CameraControl, + this.createCaptureRequestOptions = _createCaptureRequestOptions, }); /// Returns a [ProcessCameraProvider] instance. @@ -137,6 +142,15 @@ class CameraXProxy { /// rotation constants. Future Function() getDefaultDisplayRotation; + /// Get [Camera2CameraControl] instance from [cameraControl]. + Camera2CameraControl Function(CameraControl cameraControl) + getCamera2CameraControl; + + /// Create [CapureRequestOptions] with specified options. + CaptureRequestOptions Function( + List<(CaptureRequestKeySupportedType, Object?)> options) + createCaptureRequestOptions; + static Future _getProcessCameraProvider() { return ProcessCameraProvider.getInstance(); } @@ -239,4 +253,14 @@ class CameraXProxy { static Future _getDefaultDisplayRotation() async { return DeviceOrientationManager.getDefaultDisplayRotation(); } + + static Camera2CameraControl _getCamera2CameraControl( + CameraControl cameraControl) { + return Camera2CameraControl(cameraControl: cameraControl); + } + + static CaptureRequestOptions _createCaptureRequestOptions( + List<(CaptureRequestKeySupportedType, Object?)> options) { + return CaptureRequestOptions(requestedOptions: options); + } } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index bc6a0d1c6a3..de3847f3670 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -8,6 +8,7 @@ import 'package:async/async.dart'; import 'package:camera_android_camerax/camera_android_camerax.dart'; import 'package:camera_android_camerax/src/analyzer.dart'; import 'package:camera_android_camerax/src/camera.dart'; +import 'package:camera_android_camerax/src/camera2_camera_control.dart'; import 'package:camera_android_camerax/src/camera_control.dart'; import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_selector.dart'; @@ -15,6 +16,7 @@ import 'package:camera_android_camerax/src/camera_state.dart'; import 'package:camera_android_camerax/src/camera_state_error.dart'; import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/camerax_proxy.dart'; +import 'package:camera_android_camerax/src/capture_request_options.dart'; import 'package:camera_android_camerax/src/device_orientation_manager.dart'; import 'package:camera_android_camerax/src/exposure_state.dart'; import 'package:camera_android_camerax/src/fallback_strategy.dart'; @@ -74,6 +76,7 @@ import 'test_camerax_library.g.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) @GenerateMocks([], customMocks: >[ MockSpec>(as: #MockLiveCameraState), @@ -1981,4 +1984,59 @@ void main() { await camera.unlockCaptureOrientation(cameraId); expect(camera.captureOrientationLocked, isFalse); }); + + test('setExposureMode sets expected controlAeLock value via Camera2 interop', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 78; + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + + // Set directly for test versus calling createCamera. + camera.camera = MockCamera(); + + // Tell plugin to create detached Camera2CameraControl and + // CaptureRequestOptions instances for testing. + camera.proxy = CameraXProxy( + getCamera2CameraControl: (CameraControl cameraControl) => + cameraControl == mockCameraControl + ? mockCamera2CameraControl + : Camera2CameraControl.detached(cameraControl: cameraControl), + createCaptureRequestOptions: + (List<(CaptureRequestKeySupportedType, Object?)> options) => + CaptureRequestOptions.detached(requestedOptions: options), + ); + + when(camera.camera!.getCameraControl()) + .thenAnswer((_) async => mockCameraControl); + + // Test auto mode. + await camera.setExposureMode(cameraId, ExposureMode.auto); + + VerificationResult verificationResult = + verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); + CaptureRequestOptions capturedCaptureRequestOptions = + verificationResult.captured.single as CaptureRequestOptions; + List<(CaptureRequestKeySupportedType, Object?)> requestedOptions = + capturedCaptureRequestOptions.requestedOptions; + expect(requestedOptions.length, equals(1)); + expect(requestedOptions.first.$1, + equals(CaptureRequestKeySupportedType.controlAeLock)); + expect(requestedOptions.first.$2, equals(false)); + + // Test locked mode. + clearInteractions(mockCamera2CameraControl); + await camera.setExposureMode(cameraId, ExposureMode.locked); + + verificationResult = + verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); + capturedCaptureRequestOptions = + verificationResult.captured.single as CaptureRequestOptions; + requestedOptions = capturedCaptureRequestOptions.requestedOptions; + expect(requestedOptions.length, equals(1)); + expect(requestedOptions.first.$1, + equals(CaptureRequestKeySupportedType.controlAeLock)); + expect(requestedOptions.first.$2, equals(true)); + }); } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index 4c90e940e71..be2541b8f92 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -8,11 +8,14 @@ import 'dart:typed_data' as _i29; import 'package:camera_android_camerax/src/analyzer.dart' as _i15; import 'package:camera_android_camerax/src/camera.dart' as _i9; +import 'package:camera_android_camerax/src/camera2_camera_control.dart' as _i38; import 'package:camera_android_camerax/src/camera_control.dart' as _i3; import 'package:camera_android_camerax/src/camera_info.dart' as _i2; import 'package:camera_android_camerax/src/camera_selector.dart' as _i22; import 'package:camera_android_camerax/src/camera_state.dart' as _i18; import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7; +import 'package:camera_android_camerax/src/capture_request_options.dart' + as _i39; import 'package:camera_android_camerax/src/exposure_state.dart' as _i5; import 'package:camera_android_camerax/src/fallback_strategy.dart' as _i23; import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i21; @@ -1317,6 +1320,38 @@ class MockZoomState extends _i1.Mock implements _i19.ZoomState { ) as double); } +/// A class which mocks [Camera2CameraControl]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockCamera2CameraControl extends _i1.Mock + implements _i38.Camera2CameraControl { + @override + _i3.CameraControl get cameraControl => (super.noSuchMethod( + Invocation.getter(#cameraControl), + returnValue: _FakeCameraControl_1( + this, + Invocation.getter(#cameraControl), + ), + returnValueForMissingStub: _FakeCameraControl_1( + this, + Invocation.getter(#cameraControl), + ), + ) as _i3.CameraControl); + + @override + _i16.Future addCaptureRequestOptions( + _i39.CaptureRequestOptions? captureRequestOptions) => + (super.noSuchMethod( + Invocation.method( + #addCaptureRequestOptions, + [captureRequestOptions], + ), + returnValue: _i16.Future.value(), + returnValueForMissingStub: _i16.Future.value(), + ) as _i16.Future); +} + /// A class which mocks [LiveData]. /// /// See the documentation for Mockito's code generation for more information. diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart index 09eb9436c7a..58d89c49b20 100644 --- a/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera2_camera_control_test.mocks.dart @@ -6,11 +6,13 @@ import 'dart:async' as _i3; import 'package:camera_android_camerax/src/camera_control.dart' as _i2; -import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i5; -import 'package:camera_android_camerax/src/capture_request_options.dart' as _i4; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i7; +import 'package:camera_android_camerax/src/capture_request_options.dart' as _i6; +import 'package:camera_android_camerax/src/focus_metering_action.dart' as _i5; +import 'package:camera_android_camerax/src/focus_metering_result.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.g.dart' as _i6; +import 'test_camerax_library.g.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -53,6 +55,37 @@ class MockCameraControl extends _i1.Mock implements _i2.CameraControl { returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); + + @override + _i3.Future<_i4.FocusMeteringResult?> startFocusAndMetering( + _i5.FocusMeteringAction? action) => + (super.noSuchMethod( + Invocation.method( + #startFocusAndMetering, + [action], + ), + returnValue: _i3.Future<_i4.FocusMeteringResult?>.value(), + ) as _i3.Future<_i4.FocusMeteringResult?>); + + @override + _i3.Future cancelFocusAndMetering() => (super.noSuchMethod( + Invocation.method( + #cancelFocusAndMetering, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future setExposureCompensationIndex(int? index) => + (super.noSuchMethod( + Invocation.method( + #setExposureCompensationIndex, + [index], + ), + returnValue: _i3.Future.value(), + ) as _i3.Future); } /// A class which mocks [CaptureRequestOptions]. @@ -60,36 +93,24 @@ class MockCameraControl extends _i1.Mock implements _i2.CameraControl { /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockCaptureRequestOptions extends _i1.Mock - implements _i4.CaptureRequestOptions { + implements _i6.CaptureRequestOptions { MockCaptureRequestOptions() { _i1.throwOnMissingStub(this); } @override - List<(_i5.CaptureRequestKeySupportedType, dynamic)> get requestedOptions => + List<(_i7.CaptureRequestKeySupportedType, Object?)> get requestedOptions => (super.noSuchMethod( Invocation.getter(#requestedOptions), - returnValue: <(_i5.CaptureRequestKeySupportedType, dynamic)>[], - ) as List<(_i5.CaptureRequestKeySupportedType, dynamic)>); - - @override - set requestedOptions( - List<(_i5.CaptureRequestKeySupportedType, dynamic)>? - _requestedOptions) => - super.noSuchMethod( - Invocation.setter( - #requestedOptions, - _requestedOptions, - ), - returnValueForMissingStub: null, - ); + returnValue: <(_i7.CaptureRequestKeySupportedType, Object?)>[], + ) as List<(_i7.CaptureRequestKeySupportedType, Object?)>); } /// A class which mocks [TestCamera2CameraControlHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestCamera2CameraControlHostApi extends _i1.Mock - implements _i6.TestCamera2CameraControlHostApi { + implements _i8.TestCamera2CameraControlHostApi { MockTestCamera2CameraControlHostApi() { _i1.throwOnMissingStub(this); } @@ -132,7 +153,7 @@ class MockTestCamera2CameraControlHostApi extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockTestInstanceManagerHostApi extends _i1.Mock - implements _i6.TestInstanceManagerHostApi { + implements _i8.TestInstanceManagerHostApi { MockTestInstanceManagerHostApi() { _i1.throwOnMissingStub(this); } diff --git a/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart b/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart index 717215ca228..4fcab1cef21 100644 --- a/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/focus_metering_action_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.3 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in camera_android_camerax/test/focus_metering_action_test.dart. // Do not manually edit this file. diff --git a/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart b/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart index d35cdc15efb..5be2875f6d4 100644 --- a/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/focus_metering_result_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.3 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in camera_android_camerax/test/focus_metering_result_test.dart. // Do not manually edit this file. diff --git a/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart b/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart index ba199f66c63..18ec9b0a1e4 100644 --- a/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/metering_point_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.3 from annotations +// Mocks generated by Mockito 5.4.4 from annotations // in camera_android_camerax/test/metering_point_test.dart. // Do not manually edit this file. From 0f2ebfb9ba6312afb9b70781e200e43a2df433d7 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 14 Feb 2024 15:30:28 -0500 Subject: [PATCH 5/9] nit --- .../camera_android_camerax/lib/src/android_camera_camerax.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 12925ca1a3f..caab07fe2c3 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -465,7 +465,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// Sets the exposure mode for taking pictures. /// - /// [cameraId] is not used + /// [cameraId] is not used. @override Future setExposureMode(int cameraId, ExposureMode mode) async { // TODO(camsim99): In a previous PR, this will be made a class field, so use that instead once it lands. From eaa939c444a981013fae84d0f046a11b49e53a50 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 21 Feb 2024 13:35:16 -0800 Subject: [PATCH 6/9] Fix test --- .../test/android_camera_camerax_test.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 35c05c0cb5f..bb94d46628a 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -2027,6 +2027,7 @@ void main() { // Set directly for test versus calling createCamera. camera.camera = MockCamera(); + camera.cameraControl = mockCameraControl; // Tell plugin to create detached Camera2CameraControl and // CaptureRequestOptions instances for testing. @@ -2040,9 +2041,6 @@ void main() { CaptureRequestOptions.detached(requestedOptions: options), ); - when(camera.camera!.getCameraControl()) - .thenAnswer((_) async => mockCameraControl); - // Test auto mode. await camera.setExposureMode(cameraId, ExposureMode.auto); From 464e6919ed518820226c4bfa59bfc4986e90464e Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 21 Feb 2024 14:12:23 -0800 Subject: [PATCH 7/9] Fix versioning --- packages/camera/camera_android_camerax/CHANGELOG.md | 2 +- packages/camera/camera_android_camerax/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 724ef97302e..c0907db173f 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.5.0+35 +## 0.5.0+36 * Implements `setExposureMode`. diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 9e151c368ef..660997d5945 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.5.0+35 +version: 0.5.0+36 environment: sdk: ^3.1.0 From 331b0366fe143ab9048af3db202653288e53799b Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 22 Feb 2024 10:33:22 -0800 Subject: [PATCH 8/9] Fix readme --- packages/camera/camera_android_camerax/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index 807281ee078..fdc50955de4 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -30,9 +30,9 @@ dependencies: and thus, the plugin will fall back to 480p if configured with a `ResolutionPreset`. -### Exposure mode configuration \[[Issue #120468][120468]\] +### Focus mode configuration \[[Issue #120467][120467]\] -`setExposureMode`is unimplemented. +`setFocusMode` is unimplemented. ### Setting maximum duration and stream options for video capture From b3e9f554cee61c6fdc0ef3baada409f04289b26e Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 26 Feb 2024 11:17:22 -0800 Subject: [PATCH 9/9] Add doc for clarification since I found this useful in testing :) --- .../camera_android_camerax/lib/src/android_camera_camerax.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 652eb9aff2b..261525113d4 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -549,6 +549,9 @@ class AndroidCameraCameraX extends CameraPlatform { /// Sets the exposure mode for taking pictures. /// + /// Setting [ExposureMode.locked] will lock current exposure point until it + /// is unset by setting [ExposureMode.auto]. + /// /// [cameraId] is not used. @override Future setExposureMode(int cameraId, ExposureMode mode) async {