mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 07:08:10 +08:00
[camerax] Implements setExposureMode
(#6110)
Implements `setExposureMode`. Fixes https://github.com/flutter/flutter/issues/120468. ~To be landed after (1) https://github.com/flutter/packages/pull/6059 then (2) https://github.com/flutter/packages/pull/6109.~ Done :)
This commit is contained in:
@ -1,3 +1,7 @@
|
||||
## 0.5.0+36
|
||||
|
||||
* Implements `setExposureMode`.
|
||||
|
||||
## 0.5.0+35
|
||||
|
||||
* Modifies `CameraInitializedEvent` that is sent when the camera is initialized to indicate that the initial focus
|
||||
|
@ -30,10 +30,6 @@ dependencies:
|
||||
and thus, the plugin will fall back to 480p if configured with a
|
||||
`ResolutionPreset`.
|
||||
|
||||
### Exposure mode configuration \[[Issue #120468][120468]\]
|
||||
|
||||
`setExposureMode`is unimplemented.
|
||||
|
||||
### Focus mode configuration \[[Issue #120467][120467]\]
|
||||
|
||||
`setFocusMode` is unimplemented.
|
||||
|
@ -29,6 +29,9 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
|
||||
@VisibleForTesting @Nullable public SystemServicesHostApiImpl systemServicesHostApiImpl;
|
||||
@VisibleForTesting @Nullable public MeteringPointHostApiImpl meteringPointHostApiImpl;
|
||||
|
||||
@VisibleForTesting @Nullable
|
||||
public Camera2CameraControlHostApiImpl camera2CameraControlHostApiImpl;
|
||||
|
||||
@VisibleForTesting
|
||||
public @Nullable DeviceOrientationManagerHostApiImpl deviceOrientationManagerHostApiImpl;
|
||||
|
||||
@ -120,6 +123,11 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
|
||||
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));
|
||||
GeneratedCameraXLibrary.FocusMeteringActionHostApi.setup(
|
||||
binaryMessenger, new FocusMeteringActionHostApiImpl(instanceManager));
|
||||
GeneratedCameraXLibrary.FocusMeteringResultHostApi.setup(
|
||||
@ -217,6 +225,9 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
|
||||
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. */
|
||||
|
@ -110,8 +110,8 @@ public class CaptureRequestOptionsHostApiImpl implements CaptureRequestOptionsHo
|
||||
Map<CaptureRequestKeySupportedType, Object> decodedOptions =
|
||||
new HashMap<CaptureRequestKeySupportedType, Object>();
|
||||
for (Map.Entry<Long, Object> option : options.entrySet()) {
|
||||
decodedOptions.put(
|
||||
CaptureRequestKeySupportedType.values()[option.getKey().intValue()], option.getValue());
|
||||
Integer index = ((Number) option.getKey()).intValue();
|
||||
decodedOptions.put(CaptureRequestKeySupportedType.values()[index], option.getValue());
|
||||
}
|
||||
instanceManager.addDartCreatedInstance(proxy.create(decodedOptions), identifier);
|
||||
}
|
||||
|
@ -169,6 +169,8 @@ public class CameraAndroidCameraxPluginTest {
|
||||
mock(ImageAnalysisHostApiImpl.class);
|
||||
final CameraControlHostApiImpl mockCameraControlHostApiImpl =
|
||||
mock(CameraControlHostApiImpl.class);
|
||||
final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl =
|
||||
mock(Camera2CameraControlHostApiImpl.class);
|
||||
|
||||
when(flutterPluginBinding.getApplicationContext()).thenReturn(mockContext);
|
||||
|
||||
@ -180,6 +182,7 @@ public class CameraAndroidCameraxPluginTest {
|
||||
plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl;
|
||||
plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl;
|
||||
plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class);
|
||||
plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl;
|
||||
|
||||
plugin.onAttachedToEngine(flutterPluginBinding);
|
||||
plugin.onDetachedFromActivityForConfigChanges();
|
||||
@ -191,6 +194,7 @@ public class CameraAndroidCameraxPluginTest {
|
||||
verify(mockImageCaptureHostApiImpl).setContext(mockContext);
|
||||
verify(mockImageAnalysisHostApiImpl).setContext(mockContext);
|
||||
verify(mockCameraControlHostApiImpl).setContext(mockContext);
|
||||
verify(mockCamera2CameraControlHostApiImpl).setContext(mockContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -259,6 +263,8 @@ public class CameraAndroidCameraxPluginTest {
|
||||
mock(CameraControlHostApiImpl.class);
|
||||
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
|
||||
mock(DeviceOrientationManagerHostApiImpl.class);
|
||||
final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl =
|
||||
mock(Camera2CameraControlHostApiImpl.class);
|
||||
final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
|
||||
mock(MeteringPointHostApiImpl.class);
|
||||
final ArgumentCaptor<PermissionsRegistry> permissionsRegistryCaptor =
|
||||
@ -277,6 +283,7 @@ public class CameraAndroidCameraxPluginTest {
|
||||
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
|
||||
plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;
|
||||
plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class);
|
||||
plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl;
|
||||
|
||||
plugin.onAttachedToEngine(flutterPluginBinding);
|
||||
plugin.onReattachedToActivityForConfigChanges(activityPluginBinding);
|
||||
@ -294,6 +301,7 @@ public class CameraAndroidCameraxPluginTest {
|
||||
verify(mockImageCaptureHostApiImpl).setContext(mockActivity);
|
||||
verify(mockImageAnalysisHostApiImpl).setContext(mockActivity);
|
||||
verify(mockCameraControlHostApiImpl).setContext(mockActivity);
|
||||
verify(mockCamera2CameraControlHostApiImpl).setContext(mockActivity);
|
||||
|
||||
// Check permissions registry reference is set.
|
||||
verify(mockSystemServicesHostApiImpl)
|
||||
@ -347,6 +355,8 @@ public class CameraAndroidCameraxPluginTest {
|
||||
final ImageCaptureHostApiImpl mockImageCaptureHostApiImpl = mock(ImageCaptureHostApiImpl.class);
|
||||
final CameraControlHostApiImpl mockCameraControlHostApiImpl =
|
||||
mock(CameraControlHostApiImpl.class);
|
||||
final Camera2CameraControlHostApiImpl mockCamera2CameraControlHostApiImpl =
|
||||
mock(Camera2CameraControlHostApiImpl.class);
|
||||
final ArgumentCaptor<PermissionsRegistry> permissionsRegistryCaptor =
|
||||
ArgumentCaptor.forClass(PermissionsRegistry.class);
|
||||
|
||||
@ -360,6 +370,7 @@ public class CameraAndroidCameraxPluginTest {
|
||||
plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl;
|
||||
plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl;
|
||||
plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class);
|
||||
plugin.camera2CameraControlHostApiImpl = mockCamera2CameraControlHostApiImpl;
|
||||
|
||||
plugin.onAttachedToEngine(flutterPluginBinding);
|
||||
plugin.onDetachedFromActivity();
|
||||
@ -371,5 +382,6 @@ public class CameraAndroidCameraxPluginTest {
|
||||
verify(mockImageCaptureHostApiImpl).setContext(mockContext);
|
||||
verify(mockImageAnalysisHostApiImpl).setContext(mockContext);
|
||||
verify(mockCameraControlHostApiImpl).setContext(mockContext);
|
||||
verify(mockCamera2CameraControlHostApiImpl).setContext(mockContext);
|
||||
}
|
||||
}
|
||||
|
@ -393,8 +393,10 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
|
||||
children: <Widget>[
|
||||
TextButton(
|
||||
style: styleAuto,
|
||||
onPressed:
|
||||
() {}, // TODO(camsim99): Add functionality back here.
|
||||
onPressed: controller != null
|
||||
? () =>
|
||||
onSetExposureModeButtonPressed(ExposureMode.auto)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
if (controller != null) {
|
||||
CameraPlatform.instance
|
||||
@ -406,8 +408,10 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
|
||||
),
|
||||
TextButton(
|
||||
style: styleLocked,
|
||||
onPressed:
|
||||
() {}, // TODO(camsim99): Add functionality back here.
|
||||
onPressed: controller != null
|
||||
? () =>
|
||||
onSetExposureModeButtonPressed(ExposureMode.locked)
|
||||
: null,
|
||||
child: const Text('LOCKED'),
|
||||
),
|
||||
TextButton(
|
||||
|
@ -14,12 +14,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';
|
||||
@ -545,6 +547,27 @@ class AndroidCameraCameraX extends CameraPlatform {
|
||||
point: point, meteringMode: FocusMeteringAction.flagAf);
|
||||
}
|
||||
|
||||
/// 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<void> setExposureMode(int cameraId, ExposureMode mode) async {
|
||||
final Camera2CameraControl camera2Control =
|
||||
proxy.getCamera2CameraControl(cameraControl);
|
||||
final bool lockExposureMode = mode == ExposureMode.locked;
|
||||
|
||||
final CaptureRequestOptions captureRequestOptions = proxy
|
||||
.createCaptureRequestOptions(<(
|
||||
CaptureRequestKeySupportedType,
|
||||
Object?
|
||||
)>[(CaptureRequestKeySupportedType.controlAeLock, lockExposureMode)]);
|
||||
|
||||
await camera2Control.addCaptureRequestOptions(captureRequestOptions);
|
||||
}
|
||||
|
||||
/// Gets the maximum supported zoom level for the selected camera.
|
||||
///
|
||||
/// [cameraId] not used.
|
||||
|
@ -5,10 +5,13 @@
|
||||
import 'dart:ui' show Size;
|
||||
|
||||
import 'analyzer.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 'capture_request_options.dart';
|
||||
import 'device_orientation_manager.dart';
|
||||
import 'fallback_strategy.dart';
|
||||
import 'focus_metering_action.dart';
|
||||
@ -52,6 +55,8 @@ class CameraXProxy {
|
||||
_startListeningForDeviceOrientationChange,
|
||||
this.setPreviewSurfaceProvider = _setPreviewSurfaceProvider,
|
||||
this.getDefaultDisplayRotation = _getDefaultDisplayRotation,
|
||||
this.getCamera2CameraControl = _getCamera2CameraControl,
|
||||
this.createCaptureRequestOptions = _createCaptureRequestOptions,
|
||||
this.createMeteringPoint = _createMeteringPoint,
|
||||
this.createFocusMeteringAction = _createFocusMeteringAction,
|
||||
});
|
||||
@ -142,6 +147,15 @@ class CameraXProxy {
|
||||
/// rotation constants.
|
||||
Future<int> Function() getDefaultDisplayRotation;
|
||||
|
||||
/// Get [Camera2CameraControl] instance from [cameraControl].
|
||||
Camera2CameraControl Function(CameraControl cameraControl)
|
||||
getCamera2CameraControl;
|
||||
|
||||
/// Create [CapureRequestOptions] with specified options.
|
||||
CaptureRequestOptions Function(
|
||||
List<(CaptureRequestKeySupportedType, Object?)> options)
|
||||
createCaptureRequestOptions;
|
||||
|
||||
/// Returns a [MeteringPoint] with the specified coordinates based on
|
||||
/// [cameraInfo].
|
||||
MeteringPoint Function(double x, double y, CameraInfo cameraInfo)
|
||||
@ -255,6 +269,16 @@ class CameraXProxy {
|
||||
return DeviceOrientationManager.getDefaultDisplayRotation();
|
||||
}
|
||||
|
||||
static Camera2CameraControl _getCamera2CameraControl(
|
||||
CameraControl cameraControl) {
|
||||
return Camera2CameraControl(cameraControl: cameraControl);
|
||||
}
|
||||
|
||||
static CaptureRequestOptions _createCaptureRequestOptions(
|
||||
List<(CaptureRequestKeySupportedType, Object?)> options) {
|
||||
return CaptureRequestOptions(requestedOptions: options);
|
||||
}
|
||||
|
||||
static MeteringPoint _createMeteringPoint(
|
||||
double x, double y, CameraInfo cameraInfo) {
|
||||
return MeteringPoint(x: x, y: y, cameraInfo: cameraInfo);
|
||||
|
@ -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
|
||||
|
@ -9,6 +9,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';
|
||||
@ -16,6 +17,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';
|
||||
@ -78,6 +80,7 @@ import 'test_camerax_library.g.dart';
|
||||
MockSpec<TestInstanceManagerHostApi>(),
|
||||
MockSpec<TestSystemServicesHostApi>(),
|
||||
MockSpec<ZoomState>(),
|
||||
MockSpec<Camera2CameraControl>(),
|
||||
])
|
||||
@GenerateMocks(<Type>[], customMocks: <MockSpec<Object>>[
|
||||
MockSpec<LiveData<CameraState>>(as: #MockLiveCameraState),
|
||||
@ -2010,6 +2013,59 @@ void main() {
|
||||
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();
|
||||
camera.cameraControl = mockCameraControl;
|
||||
|
||||
// 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),
|
||||
);
|
||||
|
||||
// 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));
|
||||
});
|
||||
|
||||
test(
|
||||
'setExposurePoint clears current auto-exposure metering point as expected',
|
||||
() async {
|
||||
|
@ -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<void> addCaptureRequestOptions(
|
||||
_i39.CaptureRequestOptions? captureRequestOptions) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addCaptureRequestOptions,
|
||||
[captureRequestOptions],
|
||||
),
|
||||
returnValue: _i16.Future<void>.value(),
|
||||
returnValueForMissingStub: _i16.Future<void>.value(),
|
||||
) as _i16.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [LiveData].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
|
Reference in New Issue
Block a user