From c9f7e2155024206a6b8d5a3fc115910a3587b5e4 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 24 Dec 2020 15:23:12 +0100 Subject: [PATCH 01/31] Added platform interface methods for setting auto exposure. --- .../camera_platform_interface/lib/src/types/exposure_mode.dart | 0 .../camera_platform_interface/test/types/exposure_mode_test.dart | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart create mode 100644 packages/camera/camera_platform_interface/test/types/exposure_mode_test.dart diff --git a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/camera/camera_platform_interface/test/types/exposure_mode_test.dart b/packages/camera/camera_platform_interface/test/types/exposure_mode_test.dart new file mode 100644 index 000000000000..e69de29bb2d1 From 532b22ff7b71a3eba0fd8eb1dd0911ecb00621dd Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 24 Dec 2020 15:24:49 +0100 Subject: [PATCH 02/31] Added platform interface methods for setting auto exposure. --- .../camera/example/ios/Flutter/.last_build_id | 1 + .../example/ios/Flutter/Flutter.podspec | 18 ++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../camera_platform_interface/CHANGELOG.md | 4 + .../lib/src/events/camera_event.dart | 24 ++- .../method_channel/method_channel_camera.dart | 59 ++++++ .../platform_interface/camera_platform.dart | 46 ++++- .../lib/src/types/exposure_mode.dart | 36 ++++ .../lib/src/types/types.dart | 1 + .../camera_platform_interface/pubspec.yaml | 2 +- .../test/camera_platform_interface_test.dart | 78 ++++++++ .../test/events/camera_event_test.dart | 67 +++++-- .../method_channel_camera_test.dart | 172 +++++++++++++++++- .../test/types/exposure_mode_test.dart | 32 ++++ 15 files changed, 529 insertions(+), 27 deletions(-) create mode 100644 packages/camera/camera/example/ios/Flutter/.last_build_id create mode 100644 packages/camera/camera/example/ios/Flutter/Flutter.podspec create mode 100644 packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/camera/camera/example/ios/Flutter/.last_build_id b/packages/camera/camera/example/ios/Flutter/.last_build_id new file mode 100644 index 000000000000..1d0b0dc32be3 --- /dev/null +++ b/packages/camera/camera/example/ios/Flutter/.last_build_id @@ -0,0 +1 @@ +5490cb309144ac61a67edda5c46bb18b \ No newline at end of file diff --git a/packages/camera/camera/example/ios/Flutter/Flutter.podspec b/packages/camera/camera/example/ios/Flutter/Flutter.podspec new file mode 100644 index 000000000000..5ca30416bac0 --- /dev/null +++ b/packages/camera/camera/example/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.description = <<-DESC +Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. + DESC + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '8.0' + s.vendored_frameworks = 'Flutter.framework' +end diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index ea9821e841f9..916d79c70ec1 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.5 + +- Added interface to support automatic exposure. + ## 1.0.4 - Added the torch option to the FlashMode enum, which when implemented indicates the flash light should be turned on continuously. diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index ab3d45545f23..590713d04e8b 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import '../../camera_platform_interface.dart'; + /// Generic Event coming from the native side of Camera. /// /// All [CameraEvent]s contain the `cameraId` that originated the event. This @@ -45,6 +47,12 @@ class CameraInitializedEvent extends CameraEvent { /// The height of the preview in pixels. final double previewHeight; + /// The default exposure mode + final ExposureMode exposureMode; + + /// Whether setting exposure points is supported. + final bool exposurePointSupported; + /// Build a CameraInitialized event triggered from the camera represented by /// `cameraId`. /// @@ -54,6 +62,8 @@ class CameraInitializedEvent extends CameraEvent { int cameraId, this.previewWidth, this.previewHeight, + this.exposureMode, + this.exposurePointSupported, ) : super(cameraId); /// Converts the supplied [Map] to an instance of the [CameraInitializedEvent] @@ -61,6 +71,8 @@ class CameraInitializedEvent extends CameraEvent { CameraInitializedEvent.fromJson(Map json) : previewWidth = json['previewWidth'], previewHeight = json['previewHeight'], + exposureMode = deserializeExposureMode(json['exposureMode']), + exposurePointSupported = json['exposurePointSupported'], super(json['cameraId']); /// Converts the [CameraInitializedEvent] instance into a [Map] instance that @@ -69,6 +81,8 @@ class CameraInitializedEvent extends CameraEvent { 'cameraId': cameraId, 'previewWidth': previewWidth, 'previewHeight': previewHeight, + 'exposureMode': serializeExposureMode(exposureMode), + 'exposurePointSupported': exposurePointSupported, }; @override @@ -78,11 +92,17 @@ class CameraInitializedEvent extends CameraEvent { other is CameraInitializedEvent && runtimeType == other.runtimeType && previewWidth == other.previewWidth && - previewHeight == other.previewHeight; + previewHeight == other.previewHeight && + exposureMode == other.exposureMode && + exposurePointSupported == other.exposurePointSupported; @override int get hashCode => - super.hashCode ^ previewWidth.hashCode ^ previewHeight.hashCode; + super.hashCode ^ + previewWidth.hashCode ^ + previewHeight.hashCode ^ + exposureMode.hashCode ^ + exposurePointSupported.hashCode; } /// An event fired when the resolution preset of the camera has changed. diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 3bf996fedb19..a8ecf2f21332 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/utils/utils.dart'; @@ -185,6 +186,62 @@ class MethodChannelCamera extends CameraPlatform { }, ); + @override + Future setExposureMode(int cameraId, ExposureMode mode) => + _channel.invokeMethod( + 'setExposureMode', + { + 'cameraId': cameraId, + 'mode': serializeExposureMode(mode), + }, + ); + + @override + Future setExposurePoint(int cameraId, Point point) { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + return _channel.invokeMethod( + 'setExposurePoint', + { + 'cameraId': cameraId, + 'reset': point == null, + 'x': point?.x, + 'y': point?.y, + }, + ); + } + + @override + Future getMinExposureOffset(int cameraId) => + _channel.invokeMethod( + 'getMinExposureOffset', + {'cameraId': cameraId}, + ); + + @override + Future getMaxExposureOffset(int cameraId) => + _channel.invokeMethod( + 'getMaxExposureOffset', + {'cameraId': cameraId}, + ); + + @override + Future getExposureOffsetStepSize(int cameraId) => + _channel.invokeMethod( + 'getExposureOffsetStepSize', + {'cameraId': cameraId}, + ); + + @override + Future setExposureOffset(int cameraId, double offset) => + _channel.invokeMethod( + 'setExposureOffset', + { + 'cameraId': cameraId, + 'offset': offset, + }, + ); + @override Future getMaxZoomLevel(int cameraId) => _channel.invokeMethod( 'getMaxZoomLevel', @@ -265,6 +322,8 @@ class MethodChannelCamera extends CameraPlatform { cameraId, call.arguments['previewWidth'], call.arguments['previewHeight'], + deserializeExposureMode(call.arguments['exposureMode']), + call.arguments['exposurePointSupported'], )); break; case 'resolution_changed': diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 6f96079dc55c..292e3b8ac4ff 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -3,9 +3,11 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:camera_platform_interface/src/types/exposure_mode.dart'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; @@ -113,6 +115,48 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('setFlashMode() is not implemented.'); } + /// Sets the exposure mode for taking pictures. + Future setExposureMode(int cameraId, ExposureMode mode) { + throw UnimplementedError('setExposureMode() is not implemented.'); + } + + /// Sets the exposure point for automatically determining the exposure value. + Future setExposurePoint(int cameraId, Point point) { + throw UnimplementedError('setExposurePoint() is not implemented.'); + } + + /// Gets the minimum supported exposure offset for the selected camera in EV units. + Future getMinExposureOffset(int cameraId) { + throw UnimplementedError('getMinExposureOffset() is not implemented.'); + } + + /// Gets the maximum supported exposure offset for the selected camera in EV units. + Future getMaxExposureOffset(int cameraId) { + throw UnimplementedError('getMaxExposureOffset() is not implemented.'); + } + + /// Gets the supported step size for exposure offset for the selected camera in EV units. + /// + /// Returns 0 when the camera supports using a free value without stepping. + Future getExposureOffsetStepSize(int cameraId) { + throw UnimplementedError('getMinExposureOffset() is not implemented.'); + } + + /// Sets the exposure offset for the selected camera. + /// + /// The supplied [offset] value should be in EV units. 1 EV unit represents a + /// doubling in brightness. It should be between the minimum and maximum offsets + /// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively. + /// Throws a `CameraException` when an illegal offset is supplied. + /// + /// When the supplied [offset] value does not align with the step size obtained + /// through `getExposureStepSize`, it will automatically be rounded to the nearest step. + /// + /// Returns the (rounded) offset value that was set. + Future setExposureOffset(int cameraId, double offset) { + throw UnimplementedError('setExposureOffset() is not implemented.'); + } + /// Gets the maximum supported zoom level for the selected camera. Future getMaxZoomLevel(int cameraId) { throw UnimplementedError('getMaxZoomLevel() is not implemented.'); @@ -126,7 +170,7 @@ abstract class CameraPlatform extends PlatformInterface { /// Set the zoom level for the selected camera. /// /// The supplied [zoom] value should be between 1.0 and the maximum supported - /// zoom level returned by the `getMaxZoomLevel`. Throws an `CameraException` + /// zoom level returned by the `getMaxZoomLevel`. Throws a `CameraException` /// when an illegal zoom level is supplied. Future setZoomLevel(int cameraId, double zoom) { throw UnimplementedError('setZoomLevel() is not implemented.'); diff --git a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart index e69de29bb2d1..836f53826479 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart @@ -0,0 +1,36 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The possible exposure modes that can be set for a camera. +enum ExposureMode { + /// Automatically determine exposure settings. + auto, + + /// Lock the currently determined exposure settings. + locked, +} + +/// Returns the exposure mode as a String. +String serializeExposureMode(ExposureMode exposureMode) { + switch (exposureMode) { + case ExposureMode.locked: + return 'locked'; + case ExposureMode.auto: + return 'auto'; + default: + throw ArgumentError('Unknown ExposureMode value'); + } +} + +/// Returns the exposure mode for a given String. +ExposureMode deserializeExposureMode(String str) { + switch (str) { + case "locked": + return ExposureMode.locked; + case "auto": + return ExposureMode.auto; + default: + throw ArgumentError('"$str" is not a valid ExposureMode value'); + } +} diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart index 3a89a1021e95..bab430eb5a69 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/types.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -6,3 +6,4 @@ export 'camera_description.dart'; export 'resolution_preset.dart'; export 'camera_exception.dart'; export 'flash_mode.dart'; +export 'exposure_mode.dart'; diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 8cb643e84ca6..998fd616d7aa 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the camera plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.0.4 +version: 1.0.5 dependencies: flutter: diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 7a6fc344503f..574fa45e7b81 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -186,6 +186,84 @@ void main() { ); }); + test( + 'Default implementation of setExposureMode() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setExposureMode(1, null), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of setExposurePoint() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setExposurePoint(1, null), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of getMinExposureOffset() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.getMinExposureOffset(1), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of getMaxExposureOffset() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.getMaxExposureOffset(1), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of getExposureOffsetStepSize() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.getExposureOffsetStepSize(1), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of setExposureOffset() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setExposureOffset(1, null), + throwsUnimplementedError, + ); + }); + test( 'Default implementation of startVideoRecording() should throw unimplemented error', () { diff --git a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart index 01b03b8e93a0..1e28fa689383 100644 --- a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart +++ b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/types/exposure_mode.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -10,11 +11,14 @@ void main() { group('CameraInitializedEvent tests', () { test('Constructor should initialize all properties', () { - final event = CameraInitializedEvent(1, 1024, 640); + final event = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); expect(event.cameraId, 1); expect(event.previewWidth, 1024); expect(event.previewHeight, 640); + expect(event.exposureMode, ExposureMode.auto); + expect(event.exposurePointSupported, true); }); test('fromJson should initialize all properties', () { @@ -22,57 +26,92 @@ void main() { 'cameraId': 1, 'previewWidth': 1024.0, 'previewHeight': 640.0, + 'exposureMode': 'auto' }); expect(event.cameraId, 1); expect(event.previewWidth, 1024); expect(event.previewHeight, 640); + expect(event.exposureMode, ExposureMode.auto); }); test('toJson should return a map with all fields', () { - final event = CameraInitializedEvent(1, 1024, 640); + final event = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); final jsonMap = event.toJson(); - expect(jsonMap.length, 3); + expect(jsonMap.length, 5); expect(jsonMap['cameraId'], 1); expect(jsonMap['previewWidth'], 1024); expect(jsonMap['previewHeight'], 640); + expect(jsonMap['exposureMode'], 'auto'); + expect(jsonMap['exposurePointSupported'], true); }); test('equals should return true if objects are the same', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640); - final secondEvent = CameraInitializedEvent(1, 1024, 640); + final firstEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final secondEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640); - final secondEvent = CameraInitializedEvent(2, 1024, 640); + final firstEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final secondEvent = + CameraInitializedEvent(2, 1024, 640, ExposureMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewWidth is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640); - final secondEvent = CameraInitializedEvent(1, 2048, 640); + final firstEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final secondEvent = + CameraInitializedEvent(1, 2048, 640, ExposureMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewHeight is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640); - final secondEvent = CameraInitializedEvent(1, 1024, 980); + final firstEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final secondEvent = + CameraInitializedEvent(1, 1024, 980, ExposureMode.auto, true); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if exposureMode is different', () { + final firstEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final secondEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.locked, true); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if exposurePointSupported is different', + () { + final firstEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final secondEvent = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, false); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { - final event = CameraInitializedEvent(1, 1024, 640); - final expectedHashCode = event.cameraId.hashCode ^ + final event = + CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final expectedHashCode = event.cameraId ^ event.previewWidth.hashCode ^ - event.previewHeight.hashCode; + event.previewHeight.hashCode ^ + event.exposureMode.hashCode ^ + event.exposurePointSupported.hashCode; expect(event.hashCode, expectedHashCode); }); diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 82b01015e4f4..3413355af9f1 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:math'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; @@ -118,8 +119,13 @@ void main() { // Act Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController - .add(CameraInitializedEvent(cameraId, 1920, 1080)); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + )); await initializeFuture; // Assert @@ -151,8 +157,13 @@ void main() { ResolutionPreset.high, ); Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController - .add(CameraInitializedEvent(cameraId, 1920, 1080)); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + )); await initializeFuture; // Act @@ -188,8 +199,13 @@ void main() { ResolutionPreset.high, ); Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController - .add(CameraInitializedEvent(cameraId, 1920, 1080)); + camera.cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + )); await initializeFuture; }); @@ -200,7 +216,13 @@ void main() { final streamQueue = StreamQueue(eventStream); // Emit test events - final event = CameraInitializedEvent(cameraId, 3840, 2160); + final event = CameraInitializedEvent( + cameraId, + 3840, + 2160, + ExposureMode.auto, + true, + ); await camera.handleMethodCall( MethodCall('initialized', event.toJson()), cameraId); @@ -304,8 +326,15 @@ void main() { ResolutionPreset.high, ); Future initializeFuture = camera.initializeCamera(cameraId); - camera.cameraEventStreamController - .add(CameraInitializedEvent(cameraId, 1920, 1080)); + camera.cameraEventStreamController.add( + CameraInitializedEvent( + cameraId, + 1920, + 1080, + ExposureMode.auto, + true, + ), + ); await initializeFuture; }); @@ -496,6 +525,131 @@ void main() { ]); }); + test('Should set the exposure mode', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureMode': null}, + ); + + // Act + await camera.setExposureMode(cameraId, ExposureMode.auto); + await camera.setExposureMode(cameraId, ExposureMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setExposureMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setExposureMode', + arguments: {'cameraId': cameraId, 'mode': 'locked'}), + ]); + }); + + test('Should set the exposure point', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposurePoint': null}, + ); + + // Act + await camera.setExposurePoint(cameraId, Point(0.5, 0.5)); + await camera.setExposurePoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setExposurePoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); + + test('Should get the min exposure offset', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMinExposureOffset': 2.0}, + ); + + // Act + final minExposureOffset = await camera.getMinExposureOffset(cameraId); + + // Assert + expect(minExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMinExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should get the max exposure offset', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getMaxExposureOffset': 2.0}, + ); + + // Act + final maxExposureOffset = await camera.getMaxExposureOffset(cameraId); + + // Assert + expect(maxExposureOffset, 2.0); + expect(channel.log, [ + isMethodCall('getMaxExposureOffset', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should get the exposure offset step size', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'getExposureOffsetStepSize': 0.25}, + ); + + // Act + final stepSize = await camera.getExposureOffsetStepSize(cameraId); + + // Assert + expect(stepSize, 0.25); + expect(channel.log, [ + isMethodCall('getExposureOffsetStepSize', arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should set the exposure offset', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setExposureOffset': 0.6}, + ); + + // Act + final actualOffset = await camera.setExposureOffset(cameraId, 0.5); + + // Assert + expect(actualOffset, 0.6); + expect(channel.log, [ + isMethodCall('setExposureOffset', arguments: { + 'cameraId': cameraId, + 'offset': 0.5, + }), + ]); + }); + test('Should build a texture widget as preview widget', () async { // Act Widget widget = camera.buildPreview(cameraId); diff --git a/packages/camera/camera_platform_interface/test/types/exposure_mode_test.dart b/packages/camera/camera_platform_interface/test/types/exposure_mode_test.dart index e69de29bb2d1..c34c1d7b4157 100644 --- a/packages/camera/camera_platform_interface/test/types/exposure_mode_test.dart +++ b/packages/camera/camera_platform_interface/test/types/exposure_mode_test.dart @@ -0,0 +1,32 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/types/exposure_mode.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('ExposureMode should contain 2 options', () { + final values = ExposureMode.values; + + expect(values.length, 2); + }); + + test("ExposureMode enum should have items in correct index", () { + final values = ExposureMode.values; + + expect(values[0], ExposureMode.auto); + expect(values[1], ExposureMode.locked); + }); + + test("serializeExposureMode() should serialize correctly", () { + expect(serializeExposureMode(ExposureMode.auto), "auto"); + expect(serializeExposureMode(ExposureMode.locked), "locked"); + }); + + test("deserializeExposureMode() should deserialize correctly", () { + expect(deserializeExposureMode('auto'), ExposureMode.auto); + expect(deserializeExposureMode('locked'), ExposureMode.locked); + }); +} From 1844b2424f17e1b8b96184fc802f0dee2ff6dfe1 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 24 Dec 2020 15:27:43 +0100 Subject: [PATCH 03/31] Remove workspace files --- .../camera/example/ios/Flutter/.last_build_id | 1 - .../camera/example/ios/Flutter/Flutter.podspec | 18 ------------------ .../xcshareddata/IDEWorkspaceChecks.plist | 8 -------- .../xcshareddata/IDEWorkspaceChecks.plist | 8 -------- 4 files changed, 35 deletions(-) delete mode 100644 packages/camera/camera/example/ios/Flutter/.last_build_id delete mode 100644 packages/camera/camera/example/ios/Flutter/Flutter.podspec delete mode 100644 packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/camera/camera/example/ios/Flutter/.last_build_id b/packages/camera/camera/example/ios/Flutter/.last_build_id deleted file mode 100644 index 1d0b0dc32be3..000000000000 --- a/packages/camera/camera/example/ios/Flutter/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -5490cb309144ac61a67edda5c46bb18b \ No newline at end of file diff --git a/packages/camera/camera/example/ios/Flutter/Flutter.podspec b/packages/camera/camera/example/ios/Flutter/Flutter.podspec deleted file mode 100644 index 5ca30416bac0..000000000000 --- a/packages/camera/camera/example/ios/Flutter/Flutter.podspec +++ /dev/null @@ -1,18 +0,0 @@ -# -# NOTE: This podspec is NOT to be published. It is only used as a local source! -# - -Pod::Spec.new do |s| - s.name = 'Flutter' - s.version = '1.0.0' - s.summary = 'High-performance, high-fidelity mobile apps.' - s.description = <<-DESC -Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. - DESC - s.homepage = 'https://flutter.io' - s.license = { :type => 'MIT' } - s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } - s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' - s.vendored_frameworks = 'Flutter.framework' -end diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d68..000000000000 --- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d68..000000000000 --- a/packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - From fd8dd401ec7a4c477be53352fcfa0ad4ff35df54 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 24 Dec 2020 15:51:09 +0100 Subject: [PATCH 04/31] Added auto exposure implementations for Android and iOS --- packages/camera/camera/CHANGELOG.md | 8 + .../io/flutter/plugins/camera/Camera.java | 363 ++++++++++++++-- .../flutter/plugins/camera/DartMessenger.java | 17 +- .../plugins/camera/MethodCallHandlerImpl.java | 68 +++ .../plugins/camera/PictureCaptureRequest.java | 4 +- .../plugins/camera/types/ExposureMode.java | 25 ++ .../plugins/camera/types/FlashMode.java | 26 +- .../plugins/camera/DartMessengerTest.java | 4 +- .../camera/PictureCaptureRequestTest.java | 12 +- .../camera/types/ExposureModeTest.java | 33 ++ .../plugins/camera/types/FlashModeTest.java | 8 + packages/camera/camera/example/lib/main.dart | 316 +++++++++++--- .../camera/camera/ios/Classes/CameraPlugin.m | 121 +++++- packages/camera/camera/lib/camera.dart | 1 + .../camera/lib/src/camera_controller.dart | 176 +++++++- packages/camera/camera/pubspec.yaml | 7 +- packages/camera/camera/test/camera_test.dart | 401 +++++++++++++++++- .../camera/camera/test/camera_value_test.dart | 30 +- 18 files changed, 1478 insertions(+), 142 deletions(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index b407b83e35db..a80b8286362a 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.6.4 + +* Adds auto exposure support for Android and iOS implementations. + +## 0.6.3+1 + +* Fixes flash & torch modes not working on some Android devices. + ## 0.6.3 * Adds torch mode as a flash mode for Android and iOS implementations. diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index cae666d6742a..e2c8ff3dc1f6 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -12,6 +12,7 @@ import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCaptureSession.CaptureCallback; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; @@ -20,6 +21,7 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.MeteringRectangle; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; import android.media.CamcorderProfile; @@ -29,13 +31,19 @@ import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import android.os.Handler; +import android.os.Looper; +import android.util.Range; +import android.util.Rational; import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; import androidx.annotation.NonNull; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugins.camera.PictureCaptureRequest.State; import io.flutter.plugins.camera.media.MediaRecorderBuilder; +import io.flutter.plugins.camera.types.ExposureMode; import io.flutter.plugins.camera.types.FlashMode; import io.flutter.plugins.camera.types.ResolutionPreset; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; @@ -76,7 +84,10 @@ public class Camera { private File videoRecordingFile; private int currentOrientation = ORIENTATION_UNKNOWN; private FlashMode flashMode; + private ExposureMode exposureMode; private PictureCaptureRequest pictureCaptureRequest; + private MeteringRectangle aeMeteringRectangle; + private int exposureOffset; public Camera( final Activity activity, @@ -96,6 +107,8 @@ public Camera( this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); this.applicationContext = activity.getApplicationContext(); this.flashMode = FlashMode.auto; + this.exposureMode = ExposureMode.auto; + this.exposureOffset = 0; orientationEventListener = new OrientationEventListener(activity.getApplicationContext()) { @Override @@ -155,14 +168,15 @@ public void onOpened(@NonNull CameraDevice device) { cameraDevice = device; try { startPreview(); + dartMessenger.sendCameraInitializedEvent( + previewSize.getWidth(), + previewSize.getHeight(), + exposureMode, + isExposurePointSupported()); } catch (CameraAccessException e) { dartMessenger.sendCameraErrorEvent(e.getMessage()); close(); - return; } - - dartMessenger.sendCameraInitializedEvent( - previewSize.getWidth(), previewSize.getHeight()); } @Override @@ -246,7 +260,7 @@ public void takePicture(@NonNull final Result result) { }, null); - runPicturePreCapture(); + runPictureAutoFocus(); } private final CameraCaptureSession.CaptureCallback pictureCaptureCallback = @@ -256,18 +270,15 @@ public void onCaptureCompleted( @NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { - assert (pictureCaptureRequest != null); - switch (pictureCaptureRequest.getState()) { - case awaitingPreCapture: - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - // Some devices might return null here, in which case we will also continue. - if (aeState == null - || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED - || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { - runPictureCapture(); - } - break; - } + processCapture(result); + } + + @Override + public void onCaptureProgressed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureResult partialResult) { + processCapture(partialResult); } @Override @@ -289,11 +300,54 @@ public void onCaptureFailed( } pictureCaptureRequest.error("captureFailure", reason, null); } + + private void processCapture(CaptureResult result) { + if (pictureCaptureRequest == null) { + return; + } + + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + switch (pictureCaptureRequest.getState()) { + case focusing: + if (afState == null) { + return; + } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED + || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { + // Some devices might return null here, in which case we will also continue. + if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { + runPictureCapture(); + } else { + runPicturePreCapture(); + } + } + break; + case preCapture: + // Some devices might return null here, in which case we will also continue. + if (aeState == null + || aeState == CaptureRequest.CONTROL_AE_STATE_PRECAPTURE + || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED + || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { + pictureCaptureRequest.setState(State.waitingPreCaptureReady); + } + break; + case waitingPreCaptureReady: + if (aeState == null || aeState != CaptureRequest.CONTROL_AE_STATE_PRECAPTURE) { + runPictureCapture(); + } + } + } }; + private void runPictureAutoFocus() { + assert (pictureCaptureRequest != null); + pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing); + lockAutoFocus(); + } + private void runPicturePreCapture() { assert (pictureCaptureRequest != null); - pictureCaptureRequest.setState(PictureCaptureRequest.State.awaitingPreCapture); + pictureCaptureRequest.setState(PictureCaptureRequest.State.preCapture); captureRequestBuilder.set( CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, @@ -331,7 +385,47 @@ private void runPictureCapture() { CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); break; } - cameraCaptureSession.capture(captureBuilder.build(), pictureCaptureCallback, null); + cameraCaptureSession.stopRepeating(); + cameraCaptureSession.capture( + captureBuilder.build(), + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + unlockAutoFocus(); + } + }, + null); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + private void lockAutoFocus() { + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); + try { + cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + private void unlockAutoFocus() { + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); + initPreviewCaptureBuilder(); + try { + cameraCaptureSession.capture(captureRequestBuilder.build(), null, null); + } catch (CameraAccessException ignored) { + } + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); + try { + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); } catch (CameraAccessException e) { pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); } @@ -377,7 +471,10 @@ public void onConfigured(@NonNull CameraCaptureSession session) { } cameraCaptureSession = session; initPreviewCaptureBuilder(); - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), + pictureCaptureCallback, + new Handler(Looper.getMainLooper())); if (onSuccessCallback != null) { onSuccessCallback.run(); } @@ -516,30 +613,216 @@ public void resumeVideoRecording(@NonNull final Result result) { public void setFlashMode(@NonNull final Result result, FlashMode mode) throws CameraAccessException { // Get the flash availability - Boolean flashAvailable; - try { - flashAvailable = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.FLASH_INFO_AVAILABLE); - } catch (CameraAccessException e) { - result.error("setFlashModeFailed", e.getMessage(), null); - return; - } + Boolean flashAvailable = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + // Check if flash is available. if (flashAvailable == null || !flashAvailable) { result.error("setFlashModeFailed", "Device does not have flash capabilities", null); return; } + + // If switching directly from torch to auto or on, make sure we turn off the torch. + if (flashMode == FlashMode.torch && mode != FlashMode.torch && mode != FlashMode.off) { + this.flashMode = FlashMode.off; + initPreviewCaptureBuilder(); + this.cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), + new CaptureCallback() { + private boolean isFinished = false; + + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult captureResult) { + if (isFinished) { + return; + } + + updateFlash(mode); + result.success(null); + isFinished = true; + } + + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + if (isFinished) { + return; + } + + result.error("setFlashModeFailed", "Could not set flash mode.", null); + isFinished = true; + } + }, + null); + } else { + updateFlash(mode); + result.success(null); + } + } + + private void updateFlash(FlashMode mode) { // Get flash - this.flashMode = mode; + flashMode = mode; + initPreviewCaptureBuilder(); + try { + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + public void setExposureMode(@NonNull final Result result, ExposureMode mode) + throws CameraAccessException { + this.exposureMode = mode; + initPreviewCaptureBuilder(); + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + result.success(null); + } + + public void setExposurePoint(@NonNull final Result result, Double x, Double y) + throws CameraAccessException { + // Check if exposure point functionality is available. + if (!isExposurePointSupported()) { + result.error( + "setExposurePointFailed", "Device does not have exposure point capabilities", null); + return; + } + // Check if we are doing a reset or not + if (x == null || y == null) { + x = 0.5; + y = 0.5; + } + // Get the current exposure point boundaries. + Size maxBoundaries = getExposureBoundaries(); + if (maxBoundaries == null) { + result.error("setExposurePointFailed", "Could not determine max region boundaries", null); + return; + } + // Interpolate the target coordinate + int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1))); + int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1))); + // Determine the dimensions of the metering triangle (1th of the viewport) + int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d); + int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d); + // Adjust target coordinate to represent top-left corner of metering rectangle + targetX -= targetWidth / 2; + targetY -= targetHeight / 2; + // Adjust target coordinate as to not fall out of bounds + if (targetX < 0) targetX = 0; + if (targetY < 0) targetY = 0; + int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth; + int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight; + if (targetX > maxTargetX) targetX = maxTargetX; + if (targetY > maxTargetY) targetY = maxTargetY; + // Set the metering rectangle + aeMeteringRectangle = new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1); + // Apply it initPreviewCaptureBuilder(); this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); result.success(null); } + @SuppressLint("NewApi") + private Size getExposureBoundaries() throws CameraAccessException { + // Check if the device supports distortion correction + boolean supportsDistortionCorrection = false; + if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.P) { + int[] availableDistortionCorrectionModes = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); + if (availableDistortionCorrectionModes == null) + availableDistortionCorrectionModes = new int[0]; + long nonOffModesSupported = + Arrays.stream(availableDistortionCorrectionModes) + .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) + .count(); + supportsDistortionCorrection = nonOffModesSupported > 0; + } + // No distortion correction support + if (!supportsDistortionCorrection) { + return cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); + } + // Get the current distortion correction mode + Integer distortionCorrectionMode = + captureRequestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); + // Return the correct boundaries depending on the mode + android.graphics.Rect rect; + if (distortionCorrectionMode == null + || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) { + rect = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); + } else { + rect = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + } + return rect == null ? null : new Size(rect.width(), rect.height()); + } + + private boolean isExposurePointSupported() throws CameraAccessException { + Integer supportedRegions = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); + return supportedRegions != null && supportedRegions > 0; + } + + public double getMinExposureOffset() throws CameraAccessException { + Range range = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + double minStepped = range == null ? 0 : range.getLower(); + double stepSize = getExposureOffsetStepSize(); + return minStepped * stepSize; + } + + public double getMaxExposureOffset() throws CameraAccessException { + Range range = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + double maxStepped = range == null ? 0 : range.getUpper(); + double stepSize = getExposureOffsetStepSize(); + return maxStepped * stepSize; + } + + public double getExposureOffsetStepSize() throws CameraAccessException { + Rational stepSize = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); + return stepSize == null ? 0.0 : stepSize.doubleValue(); + } + + public void setExposureOffset(@NonNull final Result result, double offset) + throws CameraAccessException { + // Set the exposure offset + double stepSize = getExposureOffsetStepSize(); + exposureOffset = (int) (offset / stepSize); + // Apply it + initPreviewCaptureBuilder(); + this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + result.success(offset); + } + private void initPreviewCaptureBuilder() { captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + // Applying flash modes switch (flashMode) { case off: captureRequestBuilder.set( @@ -563,6 +846,24 @@ private void initPreviewCaptureBuilder() { captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); break; } + // Applying auto exposure + if (aeMeteringRectangle != null) { + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {aeMeteringRectangle}); + } + switch (exposureMode) { + case locked: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); + break; + case auto: + default: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); + break; + } + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureOffset); + // Applying auto focus + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); } public void startPreview() throws CameraAccessException { diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 49f9d9a76de0..2fee13816b51 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugins.camera.types.ExposureMode; import java.util.HashMap; import java.util.Map; @@ -20,13 +21,23 @@ enum EventType { channel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId); } - void sendCameraInitializedEvent(Integer previewWidth, Integer previewHeight) { + void sendCameraInitializedEvent( + Integer previewWidth, + Integer previewHeight, + ExposureMode exposureMode, + Boolean exposurePointSupported) { + assert (previewWidth != null); + assert (previewHeight != null); + assert (exposureMode != null); + assert (exposurePointSupported != null); this.send( EventType.INITIALIZED, new HashMap() { { - if (previewWidth != null) put("previewWidth", previewWidth.doubleValue()); - if (previewHeight != null) put("previewHeight", previewHeight.doubleValue()); + put("previewWidth", previewWidth.doubleValue()); + put("previewHeight", previewHeight.doubleValue()); + put("exposureMode", exposureMode.toString()); + put("exposurePointSupported", exposurePointSupported); } }); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 704504176518..78a10010f90b 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -10,6 +10,7 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; +import io.flutter.plugins.camera.types.ExposureMode; import io.flutter.plugins.camera.types.FlashMode; import io.flutter.view.TextureRegistry; import java.util.HashMap; @@ -138,6 +139,73 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } break; } + case "setExposureMode": + { + String modeStr = call.argument("mode"); + ExposureMode mode = ExposureMode.getValueForString(modeStr); + if (mode == null) { + result.error("setExposureModeFailed", "Unknown exposure mode " + modeStr, null); + return; + } + try { + camera.setExposureMode(result, mode); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "setExposurePoint": + { + Boolean reset = call.argument("reset"); + Double x = null; + Double y = null; + if (reset == null || !reset) { + x = call.argument("x"); + y = call.argument("y"); + } + try { + camera.setExposurePoint(result, x, y); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "getMinExposureOffset": + { + try { + result.success(camera.getMinExposureOffset()); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "getMaxExposureOffset": + { + try { + result.success(camera.getMaxExposureOffset()); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "getExposureOffsetStepSize": + { + try { + result.success(camera.getExposureOffsetStepSize()); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "setExposureOffset": + { + try { + camera.setExposureOffset(result, call.argument("offset")); + } catch (Exception e) { + handleException(e, result); + } + break; + } case "startImageStream": { try { diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java index e365f071d9a8..1103b8583ad6 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java @@ -7,7 +7,9 @@ class PictureCaptureRequest { enum State { idle, - awaitingPreCapture, + focusing, + preCapture, + waitingPreCaptureReady, capturing, finished, error, diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java new file mode 100644 index 000000000000..8066f59d2b14 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java @@ -0,0 +1,25 @@ +package io.flutter.plugins.camera.types; + +// Mirrors exposure_mode.dart +public enum ExposureMode { + auto("auto"), + locked("locked"); + + private final String strValue; + + ExposureMode(String strValue) { + this.strValue = strValue; + } + + public static ExposureMode getValueForString(String modeStr) { + for (ExposureMode value : values()) { + if (value.strValue.equals(modeStr)) return value; + } + return null; + } + + @Override + public String toString() { + return strValue; + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java index 99d4915b3a6a..ee6fe489511f 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java @@ -2,16 +2,26 @@ // Mirrors flash_mode.dart public enum FlashMode { - off, - auto, - always, - torch; + off("off"), + auto("auto"), + always("always"), + torch("torch"); + + private final String strValue; + + FlashMode(String strValue) { + this.strValue = strValue; + } public static FlashMode getValueForString(String modeStr) { - try { - return valueOf(modeStr); - } catch (IllegalArgumentException | NullPointerException e) { - return null; + for (FlashMode value : values()) { + if (value.strValue.equals(modeStr)) return value; } + return null; + } + + @Override + public String toString() { + return strValue; } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index a689f2b6128f..52d9829f7d8f 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -58,7 +58,7 @@ public void sendCameraErrorEvent_includesErrorDescriptions() { @Test public void sendCameraInitializedEvent_includesPreviewSize() { - dartMessenger.sendCameraInitializedEvent(0, 0); + dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, true); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); @@ -66,6 +66,8 @@ public void sendCameraInitializedEvent_includesPreviewSize() { assertEquals("initialized", call.method); assertEquals(0, (double) call.argument("previewWidth"), 0); assertEquals(0, (double) call.argument("previewHeight"), 0); + assertEquals("ExposureMode auto", call.argument("exposureMode"), "auto"); + assertEquals("exposurePointSupported", call.argument("exposurePointSupported"), true); } @Test diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java index 2b6aa0f25fcf..2356b306c6c4 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java @@ -20,11 +20,15 @@ public void state_is_idle_by_default() { @Test public void setState_sets_state() { PictureCaptureRequest req = new PictureCaptureRequest(null); - req.setState(PictureCaptureRequest.State.awaitingPreCapture); + req.setState(PictureCaptureRequest.State.focusing); + assertEquals("State is focusing", req.getState(), PictureCaptureRequest.State.focusing); + req.setState(PictureCaptureRequest.State.preCapture); + assertEquals("State is preCapture", req.getState(), PictureCaptureRequest.State.preCapture); + req.setState(PictureCaptureRequest.State.waitingPreCaptureReady); assertEquals( - "State is awaitingPreCapture", + "State is waitingPreCaptureReady", req.getState(), - PictureCaptureRequest.State.awaitingPreCapture); + PictureCaptureRequest.State.waitingPreCaptureReady); req.setState(PictureCaptureRequest.State.capturing); assertEquals( "State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing); @@ -49,7 +53,7 @@ public void isFinished_is_true_When_state_is_finished_or_error() { // Test false states req.setState(PictureCaptureRequest.State.idle); assertFalse(req.isFinished()); - req.setState(PictureCaptureRequest.State.awaitingPreCapture); + req.setState(PictureCaptureRequest.State.preCapture); assertFalse(req.isFinished()); req.setState(PictureCaptureRequest.State.capturing); assertFalse(req.isFinished()); diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java new file mode 100644 index 000000000000..28d2343cedcd --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java @@ -0,0 +1,33 @@ +package io.flutter.plugins.camera.types; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ExposureModeTest { + + @Test + public void getValueForString_returns_correct_values() { + assertEquals( + "Returns ExposureMode.auto for 'auto'", + ExposureMode.getValueForString("auto"), + ExposureMode.auto); + assertEquals( + "Returns ExposureMode.locked for 'locked'", + ExposureMode.getValueForString("locked"), + ExposureMode.locked); + } + + @Test + public void getValueForString_returns_null_for_nonexistant_value() { + assertEquals( + "Returns null for 'nonexistant'", ExposureMode.getValueForString("nonexistant"), null); + } + + @Test + public void toString_returns_correct_value() { + assertEquals("Returns 'auto' for ExposureMode.auto", ExposureMode.auto.toString(), "auto"); + assertEquals( + "Returns 'locked' for ExposureMode.locked", ExposureMode.locked.toString(), "locked"); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java index d2674e8c7e06..bba01836545a 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java @@ -27,4 +27,12 @@ public void getValueForString_returns_null_for_nonexistant_value() { assertEquals( "Returns null for 'nonexistant'", FlashMode.getValueForString("nonexistant"), null); } + + @Test + public void toString_returns_correct_value() { + assertEquals("Returns 'off' for FlashMode.off", FlashMode.off.toString(), "off"); + assertEquals("Returns 'auto' for FlashMode.auto", FlashMode.auto.toString(), "auto"); + assertEquals("Returns 'always' for FlashMode.always", FlashMode.always.toString(), "always"); + assertEquals("Returns 'torch' for FlashMode.torch", FlashMode.torch.toString(), "torch"); + } } diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index ee8e2c259b3d..c4fa1c5ed01e 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -35,13 +35,20 @@ void logError(String code, String message) => print('Error: $code\nError Message: $message'); class _CameraExampleHomeState extends State - with WidgetsBindingObserver { + with WidgetsBindingObserver, TickerProviderStateMixin { CameraController controller; XFile imageFile; XFile videoFile; VideoPlayerController videoController; VoidCallback videoPlayerListener; bool enableAudio = true; + double _minAvailableExposureOffset = 0.0; + double _maxAvailableExposureOffset = 0.0; + double _currentExposureOffset = 0.0; + AnimationController _flashModeControlRowAnimationController; + Animation _flashModeControlRowAnimation; + AnimationController _exposureModeControlRowAnimationController; + Animation _exposureModeControlRowAnimation; double _minAvailableZoom; double _maxAvailableZoom; double _currentScale = 1.0; @@ -54,11 +61,29 @@ class _CameraExampleHomeState extends State void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); + _flashModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _flashModeControlRowAnimation = CurvedAnimation( + parent: _flashModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + _exposureModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _exposureModeControlRowAnimation = CurvedAnimation( + parent: _exposureModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); + _flashModeControlRowAnimationController.dispose(); + _exposureModeControlRowAnimationController.dispose(); super.dispose(); } @@ -108,8 +133,7 @@ class _CameraExampleHomeState extends State ), ), _captureControlRowWidget(), - _flashModeRowWidget(), - _toggleAudioWidget(), + _modeControlRowWidget(), Padding( padding: const EdgeInsets.all(5.0), child: Row( @@ -142,11 +166,15 @@ class _CameraExampleHomeState extends State child: Listener( onPointerDown: (_) => _pointers++, onPointerUp: (_) => _pointers--, - child: GestureDetector( - onScaleStart: _handleScaleStart, - onScaleUpdate: _handleScaleUpdate, - child: CameraPreview(controller), - ), + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return GestureDetector( + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + onTapDown: (details) => onViewFinderTap(details, constraints), + child: CameraPreview(controller), + ); + }), ), ); } @@ -168,27 +196,6 @@ class _CameraExampleHomeState extends State await controller.setZoomLevel(_currentScale); } - /// Toggle recording audio - Widget _toggleAudioWidget() { - return Padding( - padding: const EdgeInsets.only(left: 25), - child: Row( - children: [ - const Text('Enable Audio:'), - Switch( - value: enableAudio, - onChanged: (bool value) { - enableAudio = value; - if (controller != null) { - onNewCameraSelected(controller.description); - } - }, - ), - ], - ), - ); - } - /// Display the thumbnail of the captured image or video. Widget _thumbnailWidget() { return Expanded( @@ -223,49 +230,156 @@ class _CameraExampleHomeState extends State ); } - /// Display a bar with buttons to change the flash mode - Widget _flashModeRowWidget() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, - children: [ - IconButton( - icon: const Icon(Icons.flash_off), - color: controller?.value?.flashMode == FlashMode.off - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onFlashModeButtonPressed(FlashMode.off) - : null, - ), - IconButton( - icon: const Icon(Icons.flash_auto), - color: controller?.value?.flashMode == FlashMode.auto - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onFlashModeButtonPressed(FlashMode.auto) - : null, + /// Display a bar with buttons to change the flash and exposure modes + Widget _modeControlRowWidget() { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + children: [ + IconButton( + icon: Icon(Icons.flash_on), + color: Colors.blue, + onPressed: controller != null ? onFlashModeButtonPressed : null, + ), + IconButton( + icon: Icon(Icons.exposure), + color: Colors.blue, + onPressed: + controller != null ? onExposureModeButtonPressed : null, + ), + IconButton( + icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), + color: Colors.blue, + onPressed: controller != null ? onAudioModeButtonPressed : null, + ), + ], ), - IconButton( - icon: const Icon(Icons.flash_on), - color: controller?.value?.flashMode == FlashMode.always - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onFlashModeButtonPressed(FlashMode.always) - : null, + _flashModeControlRowWidget(), + _exposureModeControlRowWidget(), + ], + ); + } + + Widget _flashModeControlRowWidget() { + return SizeTransition( + sizeFactor: _flashModeControlRowAnimation, + child: ClipRect( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + children: [ + IconButton( + icon: Icon(Icons.flash_off), + color: controller?.value?.flashMode == FlashMode.off + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.off) + : null, + ), + IconButton( + icon: Icon(Icons.flash_auto), + color: controller?.value?.flashMode == FlashMode.auto + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.auto) + : null, + ), + IconButton( + icon: Icon(Icons.flash_on), + color: controller?.value?.flashMode == FlashMode.always + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.always) + : null, + ), + IconButton( + icon: Icon(Icons.highlight), + color: controller?.value?.flashMode == FlashMode.torch + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFlashModeButtonPressed(FlashMode.torch) + : null, + ), + ], ), - IconButton( - icon: const Icon(Icons.highlight), - color: controller?.value?.flashMode == FlashMode.torch - ? Colors.orange - : Colors.blue, - onPressed: controller != null - ? () => onFlashModeButtonPressed(FlashMode.torch) - : null, + ), + ); + } + + Widget _exposureModeControlRowWidget() { + return SizeTransition( + sizeFactor: _exposureModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + Center( + child: Text("Exposure Mode"), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + children: [ + FlatButton( + child: Text('AUTO'), + textColor: + controller?.value?.exposureMode == ExposureMode.auto + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.auto) + : null, + onLongPress: () { + if (controller != null) controller.setExposurePoint(null); + showInSnackBar('Resetting exposure point'); + }, + ), + FlatButton( + child: Text('LOCKED'), + textColor: + controller?.value?.exposureMode == ExposureMode.locked + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => + onSetExposureModeButtonPressed(ExposureMode.locked) + : null, + ), + ], + ), + Center( + child: Text("Exposure Offset"), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + children: [ + Text(_minAvailableExposureOffset.toString()), + Slider( + value: _currentExposureOffset, + min: _minAvailableExposureOffset, + max: _maxAvailableExposureOffset, + label: _currentExposureOffset.toString(), + onChanged: _minAvailableExposureOffset == + _maxAvailableExposureOffset + ? null + : setExposureOffset, + ), + Text(_maxAvailableExposureOffset.toString()), + ], + ), + ], + ), ), - ], + ), ); } @@ -353,6 +467,13 @@ class _CameraExampleHomeState extends State _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message))); } + void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { + controller.setExposurePoint(Offset( + details.localPosition.dx / constraints.maxWidth, + details.localPosition.dy / constraints.maxHeight, + )); + } + void onNewCameraSelected(CameraDescription cameraDescription) async { if (controller != null) { await controller.dispose(); @@ -373,6 +494,8 @@ class _CameraExampleHomeState extends State try { await controller.initialize(); + _minAvailableExposureOffset = await controller.getMinExposureOffset(); + _maxAvailableExposureOffset = await controller.getMaxExposureOffset(); _maxAvailableZoom = await controller.getMaxZoomLevel(); _minAvailableZoom = await controller.getMinZoomLevel(); } on CameraException catch (e) { @@ -397,13 +520,45 @@ class _CameraExampleHomeState extends State }); } - void onFlashModeButtonPressed(FlashMode mode) { + void onFlashModeButtonPressed() { + if (_flashModeControlRowAnimationController.value == 1) { + _flashModeControlRowAnimationController.reverse(); + } else { + _flashModeControlRowAnimationController.forward(); + _exposureModeControlRowAnimationController.reverse(); + } + } + + void onExposureModeButtonPressed() { + if (_exposureModeControlRowAnimationController.value == 1) { + _exposureModeControlRowAnimationController.reverse(); + } else { + _exposureModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + } + } + + void onAudioModeButtonPressed() { + enableAudio = !enableAudio; + if (controller != null) { + onNewCameraSelected(controller.description); + } + } + + void onSetFlashModeButtonPressed(FlashMode mode) { setFlashMode(mode).then((_) { if (mounted) setState(() {}); showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); }); } + void onSetExposureModeButtonPressed(ExposureMode mode) { + setExposureMode(mode).then((_) { + if (mounted) setState(() {}); + showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); + }); + } + void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) setState(() {}); @@ -502,6 +657,27 @@ class _CameraExampleHomeState extends State } } + Future setExposureMode(ExposureMode mode) async { + try { + await controller.setExposureMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setExposureOffset(double offset) async { + setState(() { + _currentExposureOffset = offset; + }); + try { + offset = await controller.setExposureOffset(offset); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + Future _startVideoPlayer() async { final VideoPlayerController vController = VideoPlayerController.file(File(videoFile.path)); diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index d54695233bdb..005ec38fcdfa 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -176,6 +176,45 @@ static AVCaptureFlashMode getAVCaptureFlashModeForFlashMode(FlashMode mode) { } } +// Mirrors ExposureMode in camera.dart +typedef enum { + ExposureModeAuto, + ExposureModeLocked, + +} ExposureMode; + +static NSString *getStringForExposureMode(ExposureMode mode) { + switch (mode) { + case ExposureModeAuto: + return @"auto"; + case ExposureModeLocked: + return @"locked"; + } + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown string for exposure mode"] + }]; + @throw error; +} + +static ExposureMode getExposureModeForString(NSString *mode) { + if ([mode isEqualToString:@"auto"]) { + return ExposureModeAuto; + } else if ([mode isEqualToString:@"locked"]) { + return ExposureModeLocked; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown exposure mode %@", mode] + }]; + @throw error; + } +} + // Mirrors ResolutionPreset in camera.dart typedef enum { veryLow, @@ -243,6 +282,7 @@ @interface FLTCam : NSObject *)messenger { if (!_isStreamingImages) { FlutterEventChannel *eventChannel = @@ -1063,7 +1159,10 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [methodChannel invokeMethod:@"initialized" arguments:@{ @"previewWidth" : @(_camera.previewSize.width), - @"previewHeight" : @(_camera.previewSize.height) + @"previewHeight" : @(_camera.previewSize.height), + @"exposureMode" : getStringForExposureMode([_camera exposureMode]), + @"exposurePointSupported" : + @([_camera.captureDevice isExposurePointOfInterestSupported]), }]; [_camera start]; result(nil); @@ -1098,6 +1197,26 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera setZoomLevel:zoom Result:result]; } else if ([@"setFlashMode" isEqualToString:call.method]) { [_camera setFlashModeWithResult:result mode:call.arguments[@"mode"]]; + } else if ([@"setExposureMode" isEqualToString:call.method]) { + [_camera setExposureModeWithResult:result mode:call.arguments[@"mode"]]; + } else if ([@"setExposurePoint" isEqualToString:call.method]) { + BOOL reset = ((NSNumber *)call.arguments[@"reset"]).boolValue; + double x = 0.5; + double y = 0.5; + if (!reset) { + x = ((NSNumber *)call.arguments[@"x"]).doubleValue; + y = ((NSNumber *)call.arguments[@"y"]).doubleValue; + } + [_camera setExposurePointWithResult:result x:x y:y]; + } else if ([@"getMinExposureOffset" isEqualToString:call.method]) { + result(@(_camera.captureDevice.minExposureTargetBias)); + } else if ([@"getMaxExposureOffset" isEqualToString:call.method]) { + result(@(_camera.captureDevice.maxExposureTargetBias)); + } else if ([@"getExposureOffsetStepSize" isEqualToString:call.method]) { + result(@(0.0)); + } else if ([@"setExposureOffset" isEqualToString:call.method]) { + [_camera setExposureOffsetWithResult:result + offset:((NSNumber *)call.arguments[@"offset"]).doubleValue]; } else { result(FlutterMethodNotImplemented); } diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 6c6214e96951..55e7aa9444aa 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -12,5 +12,6 @@ export 'package:camera_platform_interface/camera_platform_interface.dart' CameraException, CameraLensDirection, FlashMode, + ExposureMode, ResolutionPreset, XFile; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 1d7aed755f42..c1f44bc9630a 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -3,12 +3,14 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:math'; import 'package:camera/camera.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:pedantic/pedantic.dart'; final MethodChannel _channel = const MethodChannel('plugins.flutter.io/camera'); @@ -37,6 +39,8 @@ class CameraValue { this.isStreamingImages, bool isRecordingPaused, this.flashMode, + this.exposureMode, + this.exposurePointSupported, }) : _isRecordingPaused = isRecordingPaused; /// Creates a new camera controller state for an uninitialized controller. @@ -48,6 +52,7 @@ class CameraValue { isStreamingImages: false, isRecordingPaused: false, flashMode: FlashMode.auto, + exposurePointSupported: false, ); /// True after [CameraController.initialize] has completed successfully. @@ -91,6 +96,12 @@ class CameraValue { /// The flash mode the camera is currently set to. final FlashMode flashMode; + /// The exposure mode the camera is currently set to. + final ExposureMode exposureMode; + + /// Whether setting the exposure point is supported. + final bool exposurePointSupported; + /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get @@ -104,6 +115,8 @@ class CameraValue { Size previewSize, bool isRecordingPaused, FlashMode flashMode, + ExposureMode exposureMode, + bool exposurePointSupported, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -114,6 +127,9 @@ class CameraValue { isStreamingImages: isStreamingImages ?? this.isStreamingImages, isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, flashMode: flashMode ?? this.flashMode, + exposureMode: exposureMode ?? this.exposureMode, + exposurePointSupported: + exposurePointSupported ?? this.exposurePointSupported, ); } @@ -125,7 +141,9 @@ class CameraValue { 'errorDescription: $errorDescription, ' 'previewSize: $previewSize, ' 'isStreamingImages: $isStreamingImages, ' - 'flashMode: $flashMode)'; + 'flashMode: $flashMode, ' + 'exposureMode: $exposureMode, ' + 'exposurePointSupported: $exposurePointSupported)'; } } @@ -184,25 +202,34 @@ class CameraController extends ValueNotifier { ); } try { + Completer _initializeCompleter = Completer(); + _cameraId = await CameraPlatform.instance.createCamera( description, resolutionPreset, enableAudio: enableAudio, ); - final previewSize = - CameraPlatform.instance.onCameraInitialized(_cameraId).map((event) { - return Size( - event.previewWidth, - event.previewHeight, - ); - }).first; + unawaited(CameraPlatform.instance + .onCameraInitialized(_cameraId) + .first + .then((event) { + _initializeCompleter.complete(event); + })); await CameraPlatform.instance.initializeCamera(_cameraId); value = value.copyWith( isInitialized: true, - previewSize: await previewSize, + previewSize: await _initializeCompleter.future + .then((CameraInitializedEvent event) => Size( + event.previewWidth, + event.previewHeight, + )), + exposureMode: await _initializeCompleter.future + .then((event) => event.exposureMode), + exposurePointSupported: await _initializeCompleter.future + .then((event) => event.exposurePointSupported), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); @@ -532,6 +559,137 @@ class CameraController extends ValueNotifier { } } + /// Sets the exposure mode for taking pictures. + Future setExposureMode(ExposureMode mode) async { + try { + await CameraPlatform.instance.setExposureMode(_cameraId, mode); + value = value.copyWith(exposureMode: mode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the exposure point for automatically determining the exposure value. + Future setExposurePoint(Offset point) async { + if (point != null && + (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { + throw ArgumentError( + 'The values of point should be anywhere between (0,0) and (1,1).'); + } + try { + await CameraPlatform.instance.setExposurePoint( + _cameraId, + point == null + ? null + : Point( + point.dx, + point.dy, + ), + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the minimum supported exposure offset for the selected camera in EV units. + Future getMinExposureOffset() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'getMinExposureOffset was called on uninitialized CameraController', + ); + } + + try { + return CameraPlatform.instance.getMinExposureOffset(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the maximum supported exposure offset for the selected camera in EV units. + Future getMaxExposureOffset() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'getMaxExposureOffset was called on uninitialized CameraController', + ); + } + + try { + return CameraPlatform.instance.getMaxExposureOffset(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the supported step size for exposure offset for the selected camera in EV units. + /// + /// Returns 0 when the camera supports using a free value without stepping. + Future getExposureOffsetStepSize() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'getExposureOffsetStepSize was called on uninitialized CameraController', + ); + } + + try { + return CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the exposure offset for the selected camera. + /// + /// The supplied [offset] value should be in EV units. 1 EV unit represents a + /// doubling in brightness. It should be between the minimum and maximum offsets + /// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively. + /// Throws a `CameraException` when an illegal offset is supplied. + /// + /// When the supplied [offset] value does not align with the step size obtained + /// through `getExposureStepSize`, it will automatically be rounded to the nearest step. + /// + /// Returns the (rounded) offset value that was set. + Future setExposureOffset(double offset) async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'setExposureOffset was called on uninitialized CameraController', + ); + } + + // Check if offset is in range + List range = + await Future.wait([getMinExposureOffset(), getMaxExposureOffset()]); + if (offset < range[0] || offset > range[1]) { + throw CameraException( + "exposureOffsetOutOfBounds", + "The provided exposure offset was outside the supported range for this device.", + ); + } + + // Round to the closest step if needed + double stepSize = await getExposureOffsetStepSize(); + if (stepSize > 0) { + double inv = 1.0 / stepSize; + double roundedOffset = (offset * inv).roundToDouble() / inv; + if (roundedOffset > range[1]) { + roundedOffset = (offset * inv).floorToDouble() / inv; + } else if (roundedOffset < range[0]) { + roundedOffset = (offset * inv).ceilToDouble() / inv; + } + offset = roundedOffset; + } + + try { + return CameraPlatform.instance.setExposureOffset(_cameraId, offset); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Releases the resources of this camera. @override Future dispose() async { diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 1bccbd4d45df..144ecc8df60d 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -2,14 +2,15 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.6.3 +version: 0.6.4 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - camera_platform_interface: ^1.0.4 - + #TODO(BeMacized): Replace with reference to pub.dev version once updated platform interface has been published. + camera_platform_interface: + path: ../camera_platform_interface dev_dependencies: path_provider: ^0.5.0 video_player: ^0.10.0 diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 43dec7374901..4b806107fb5b 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:math'; import 'dart:ui'; import 'package:camera/camera.dart'; @@ -26,7 +27,13 @@ get mockAvailableCameras => [ get mockInitializeCamera => 13; -get mockOnCameraInitializedEvent => CameraInitializedEvent(13, 75, 75); +get mockOnCameraInitializedEvent => CameraInitializedEvent( + 13, + 75, + 75, + ExposureMode.auto, + true, + ); get mockOnCameraClosingEvent => null; @@ -603,6 +610,398 @@ void main() { 'This is a test error message', ))); }); + + test('setExposureMode() calls $CameraPlatform', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.setExposureMode(ExposureMode.auto); + + verify(CameraPlatform.instance + .setExposureMode(cameraController.cameraId, ExposureMode.auto)) + .called(1); + }); + + test('setExposureMode() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .setExposureMode(cameraController.cameraId, ExposureMode.auto)) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposureMode(ExposureMode.auto), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); + + test('setExposurePoint() calls $CameraPlatform', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.setExposurePoint(Offset(0.5, 0.5)); + + verify(CameraPlatform.instance.setExposurePoint( + cameraController.cameraId, Point(0.5, 0.5))) + .called(1); + }); + + test('setExposurePoint() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance.setExposurePoint( + cameraController.cameraId, Point(0.5, 0.5))) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposurePoint(Offset(0.5, 0.5)), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); + + test('getMinExposureOffset() calls $CameraPlatform', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.getMinExposureOffset(); + + verify(CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId)) + .called(1); + }); + + test('getMinExposureOffset() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId)) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getMinExposureOffset(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); + + test('getMaxExposureOffset() calls $CameraPlatform', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.getMaxExposureOffset(); + + verify(CameraPlatform.instance + .getMaxExposureOffset(cameraController.cameraId)) + .called(1); + }); + + test('getMaxExposureOffset() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getMaxExposureOffset(cameraController.cameraId)) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getMaxExposureOffset(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); + + test('getExposureOffsetStepSize() calls $CameraPlatform', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController.getExposureOffsetStepSize(); + + verify(CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId)) + .called(1); + }); + + test( + 'getExposureOffsetStepSize() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getExposureOffsetStepSize(cameraController.cameraId)) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getExposureOffsetStepSize(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); + + test('setExposureOffset() calls $CameraPlatform', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + when(CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId)) + .thenAnswer((_) async => -1.0); + when(CameraPlatform.instance + .getMaxExposureOffset(cameraController.cameraId)) + .thenAnswer((_) async => 2.0); + when(CameraPlatform.instance + .getExposureOffsetStepSize(cameraController.cameraId)) + .thenAnswer((_) async => 1.0); + + await cameraController.setExposureOffset(1.0); + + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, 1.0)) + .called(1); + }); + + test('setExposureOffset() throws $CameraException on $PlatformException', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + when(CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId)) + .thenAnswer((_) async => -1.0); + when(CameraPlatform.instance + .getMaxExposureOffset(cameraController.cameraId)) + .thenAnswer((_) async => 2.0); + when(CameraPlatform.instance + .getExposureOffsetStepSize(cameraController.cameraId)) + .thenAnswer((_) async => 1.0); + when(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, 1.0)) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposureOffset(1.0), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); + + test( + 'setExposureOffset() throws $CameraException when offset is out of bounds', + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + when(CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId)) + .thenAnswer((_) async => -1.0); + when(CameraPlatform.instance + .getMaxExposureOffset(cameraController.cameraId)) + .thenAnswer((_) async => 2.0); + when(CameraPlatform.instance + .getExposureOffsetStepSize(cameraController.cameraId)) + .thenAnswer((_) async => 1.0); + + expect( + cameraController.setExposureOffset(3.0), + throwsA(isA().having( + (error) => error.description, + 'exposureOffsetOutOfBounds', + 'The provided exposure offset was outside the supported range for this device.', + ))); + expect( + cameraController.setExposureOffset(-2.0), + throwsA(isA().having( + (error) => error.description, + 'exposureOffsetOutOfBounds', + 'The provided exposure offset was outside the supported range for this device.', + ))); + + await cameraController.setExposureOffset(2.0); + await cameraController.setExposureOffset(-1.0); + await cameraController.setExposureOffset(-0.0); + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, 2.0)) + .called(1); + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, -1.0)) + .called(1); + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, 0.0)) + .called(1); + }); + + test('setExposureOffset() rounds offset to nearest step', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + when(CameraPlatform.instance + .getMinExposureOffset(cameraController.cameraId)) + .thenAnswer((_) async => -1.0); + when(CameraPlatform.instance + .getMaxExposureOffset(cameraController.cameraId)) + .thenAnswer((_) async => 1.0); + when(CameraPlatform.instance + .getExposureOffsetStepSize(cameraController.cameraId)) + .thenAnswer((_) async => 0.4); + when(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, 1.0)) + .thenAnswer((_) async => 1.0); + + await cameraController.setExposureOffset(1.0); + await cameraController.setExposureOffset(-1.0); + await cameraController.setExposureOffset(0.1); + await cameraController.setExposureOffset(0.2); + await cameraController.setExposureOffset(0.3); + await cameraController.setExposureOffset(0.4); + await cameraController.setExposureOffset(0.5); + await cameraController.setExposureOffset(0.6); + await cameraController.setExposureOffset(0.7); + await cameraController.setExposureOffset(-0.1); + await cameraController.setExposureOffset(-0.2); + await cameraController.setExposureOffset(-0.3); + await cameraController.setExposureOffset(-0.4); + await cameraController.setExposureOffset(-0.5); + await cameraController.setExposureOffset(-0.6); + await cameraController.setExposureOffset(-0.7); + + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, 0.8)) + .called(3); + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, -0.8)) + .called(3); + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, 0.0)) + .called(2); + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, 0.4)) + .called(4); + verify(CameraPlatform.instance + .setExposureOffset(cameraController.cameraId, -0.4)) + .called(4); + }); }); } diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index 06b327cb1c29..d9193e212ea9 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -13,15 +13,16 @@ void main() { group('camera_value', () { test('Can be created', () { var cameraValue = const CameraValue( - isInitialized: false, - errorDescription: null, - previewSize: Size(10, 10), - isRecordingPaused: false, - isRecordingVideo: false, - isTakingPicture: false, - isStreamingImages: false, - flashMode: FlashMode.auto, - ); + isInitialized: false, + errorDescription: null, + previewSize: Size(10, 10), + isRecordingPaused: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + flashMode: FlashMode.auto, + exposureMode: ExposureMode.auto, + exposurePointSupported: true); expect(cameraValue, isA()); expect(cameraValue.isInitialized, isFalse); @@ -31,6 +32,9 @@ void main() { expect(cameraValue.isRecordingVideo, isFalse); expect(cameraValue.isTakingPicture, isFalse); expect(cameraValue.isStreamingImages, isFalse); + expect(cameraValue.flashMode, FlashMode.auto); + expect(cameraValue.exposureMode, ExposureMode.auto); + expect(cameraValue.exposurePointSupported, true); }); test('Can be created as uninitialized', () { @@ -44,6 +48,9 @@ void main() { expect(cameraValue.isRecordingVideo, isFalse); expect(cameraValue.isTakingPicture, isFalse); expect(cameraValue.isStreamingImages, isFalse); + expect(cameraValue.flashMode, FlashMode.auto); + expect(cameraValue.exposureMode, null); + expect(cameraValue.exposurePointSupported, false); }); test('Can be copied with isInitialized', () { @@ -59,6 +66,8 @@ void main() { expect(cameraValue.isTakingPicture, isFalse); expect(cameraValue.isStreamingImages, isFalse); expect(cameraValue.flashMode, FlashMode.auto); + expect(cameraValue.exposureMode, null); + expect(cameraValue.exposurePointSupported, false); }); test('Has aspectRatio after setting size', () { @@ -97,10 +106,11 @@ void main() { isTakingPicture: false, isStreamingImages: false, flashMode: FlashMode.auto, + exposurePointSupported: true, ); expect(cameraValue.toString(), - 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto)'); + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: null, exposurePointSupported: true)'); }); }); } From 92966de06d05496b9e27037a50ddd5eb0c78e423 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 10:01:30 +0100 Subject: [PATCH 05/31] Added platform interface methods for managing auto focus. --- .../lib/src/events/camera_event.dart | 38 +++++--- .../method_channel/method_channel_camera.dart | 28 ++++++ .../platform_interface/camera_platform.dart | 14 ++- .../lib/src/types/focus_mode.dart | 36 +++++++ .../test/camera_platform_interface_test.dart | 26 ++++++ .../test/events/camera_event_test.dart | 93 ++++++++++--------- .../method_channel_camera_test.dart | 59 ++++++++++++ .../test/types/focus_mode_test.dart | 31 +++++++ 8 files changed, 267 insertions(+), 58 deletions(-) create mode 100644 packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart create mode 100644 packages/camera/camera_platform_interface/test/types/focus_mode_test.dart diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index 590713d04e8b..67324ed4635b 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_platform_interface/src/types/focus_mode.dart'; + import '../../camera_platform_interface.dart'; /// Generic Event coming from the native side of Camera. @@ -30,10 +32,7 @@ abstract class CameraEvent { @override bool operator ==(Object other) => - identical(this, other) || - other is CameraEvent && - runtimeType == other.runtimeType && - cameraId == other.cameraId; + identical(this, other) || other is CameraEvent && runtimeType == other.runtimeType && cameraId == other.cameraId; @override int get hashCode => cameraId.hashCode; @@ -50,9 +49,15 @@ class CameraInitializedEvent extends CameraEvent { /// The default exposure mode final ExposureMode exposureMode; + /// The default focus mode + final FocusMode focusMode; + /// Whether setting exposure points is supported. final bool exposurePointSupported; + /// Whether setting focus points is supported. + final bool focusPointSupported; + /// Build a CameraInitialized event triggered from the camera represented by /// `cameraId`. /// @@ -63,7 +68,9 @@ class CameraInitializedEvent extends CameraEvent { this.previewWidth, this.previewHeight, this.exposureMode, + this.focusMode, this.exposurePointSupported, + this.focusPointSupported, ) : super(cameraId); /// Converts the supplied [Map] to an instance of the [CameraInitializedEvent] @@ -72,7 +79,9 @@ class CameraInitializedEvent extends CameraEvent { : previewWidth = json['previewWidth'], previewHeight = json['previewHeight'], exposureMode = deserializeExposureMode(json['exposureMode']), + focusMode = deserializeFocusMode(json['focusMode']), exposurePointSupported = json['exposurePointSupported'], + focusPointSupported = json['focusPointSupported'], super(json['cameraId']); /// Converts the [CameraInitializedEvent] instance into a [Map] instance that @@ -82,7 +91,9 @@ class CameraInitializedEvent extends CameraEvent { 'previewWidth': previewWidth, 'previewHeight': previewHeight, 'exposureMode': serializeExposureMode(exposureMode), + 'focusMode': serializeFocusMode(focusMode), 'exposurePointSupported': exposurePointSupported, + 'focusPointSupported': focusPointSupported, }; @override @@ -94,7 +105,9 @@ class CameraInitializedEvent extends CameraEvent { previewWidth == other.previewWidth && previewHeight == other.previewHeight && exposureMode == other.exposureMode && - exposurePointSupported == other.exposurePointSupported; + focusMode == other.focusMode && + exposurePointSupported == other.exposurePointSupported && + focusPointSupported == other.focusPointSupported; @override int get hashCode => @@ -102,7 +115,9 @@ class CameraInitializedEvent extends CameraEvent { previewWidth.hashCode ^ previewHeight.hashCode ^ exposureMode.hashCode ^ - exposurePointSupported.hashCode; + focusMode.hashCode ^ + exposurePointSupported.hashCode ^ + focusPointSupported.hashCode; } /// An event fired when the resolution preset of the camera has changed. @@ -149,8 +164,7 @@ class CameraResolutionChangedEvent extends CameraEvent { captureHeight == other.captureHeight; @override - int get hashCode => - super.hashCode ^ captureWidth.hashCode ^ captureHeight.hashCode; + int get hashCode => super.hashCode ^ captureWidth.hashCode ^ captureHeight.hashCode; } /// An event fired when the camera is going to close. @@ -161,8 +175,7 @@ class CameraClosingEvent extends CameraEvent { /// Converts the supplied [Map] to an instance of the [CameraClosingEvent] /// class. - CameraClosingEvent.fromJson(Map json) - : super(json['cameraId']); + CameraClosingEvent.fromJson(Map json) : super(json['cameraId']); /// Converts the [CameraClosingEvent] instance into a [Map] instance that can /// be serialized to JSON. @@ -172,10 +185,7 @@ class CameraClosingEvent extends CameraEvent { @override bool operator ==(Object other) => - identical(this, other) || - super == (other) && - other is CameraClosingEvent && - runtimeType == other.runtimeType; + identical(this, other) || super == (other) && other is CameraClosingEvent && runtimeType == other.runtimeType; @override int get hashCode => super.hashCode; diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index a8ecf2f21332..ae865d67d5f4 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:camera_platform_interface/src/types/focus_mode.dart'; import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; @@ -242,6 +243,31 @@ class MethodChannelCamera extends CameraPlatform { }, ); + @override + Future setFocusMode(int cameraId, FocusMode mode) => + _channel.invokeMethod( + 'setFocusMode', + { + 'cameraId': cameraId, + 'mode': serializeFocusMode(mode), + }, + ); + + @override + Future setFocusPoint(int cameraId, Point point) { + assert(point == null || point.x >= 0 && point.x <= 1); + assert(point == null || point.y >= 0 && point.y <= 1); + return _channel.invokeMethod( + 'setFocusPoint', + { + 'cameraId': cameraId, + 'reset': point == null, + 'x': point?.x, + 'y': point?.y, + }, + ); + } + @override Future getMaxZoomLevel(int cameraId) => _channel.invokeMethod( 'getMaxZoomLevel', @@ -323,7 +349,9 @@ class MethodChannelCamera extends CameraPlatform { call.arguments['previewWidth'], call.arguments['previewHeight'], deserializeExposureMode(call.arguments['exposureMode']), + deserializeFocusMode(call.arguments['focusMode']), call.arguments['exposurePointSupported'], + call.arguments['focusPointSupported'], )); break; case 'resolution_changed': diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 292e3b8ac4ff..e40040c76e11 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -8,6 +8,7 @@ import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; import 'package:camera_platform_interface/src/types/exposure_mode.dart'; +import 'package:camera_platform_interface/src/types/focus_mode.dart'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; @@ -120,7 +121,7 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('setExposureMode() is not implemented.'); } - /// Sets the exposure point for automatically determining the exposure value. + /// Sets the exposure point for automatically determining the exposure values. Future setExposurePoint(int cameraId, Point point) { throw UnimplementedError('setExposurePoint() is not implemented.'); } @@ -157,6 +158,17 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('setExposureOffset() is not implemented.'); } + /// Sets the focus mode for taking pictures. + Future setFocusMode(int cameraId, FocusMode mode) { + throw UnimplementedError('setFocusMode() is not implemented.'); + } + + /// Sets the focus point for automatically determining the focus values. + Future setFocusPoint(int cameraId, Point point) { + throw UnimplementedError('setFocusPoint() is not implemented.'); + } + + /// Gets the maximum supported zoom level for the selected camera. Future getMaxZoomLevel(int cameraId) { throw UnimplementedError('getMaxZoomLevel() is not implemented.'); diff --git a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart new file mode 100644 index 000000000000..8da2a90fa858 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart @@ -0,0 +1,36 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The possible focus modes that can be set for a camera. +enum FocusMode { + /// Automatically determine focus settings. + auto, + + /// Lock the currently determined focus settings. + locked, +} + +/// Returns the focus mode as a String. +String serializeFocusMode(FocusMode focusMode) { + switch (focusMode) { + case FocusMode.locked: + return 'locked'; + case FocusMode.auto: + return 'auto'; + default: + throw ArgumentError('Unknown FocusMode value'); + } +} + +/// Returns the focus mode for a given String. +FocusMode deserializeFocusMode(String str) { + switch (str) { + case "locked": + return FocusMode.locked; + case "auto": + return FocusMode.auto; + default: + throw ArgumentError('"$str" is not a valid FocusMode value'); + } +} diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 574fa45e7b81..9aea321ca4c9 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -264,6 +264,32 @@ void main() { ); }); + test( + 'Default implementation of setFocusMode() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setFocusMode(1, null), + throwsUnimplementedError, + ); + }); + + test( + 'Default implementation of setFocusPoint() should throw unimplemented error', + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setFocusPoint(1, null), + throwsUnimplementedError, + ); + }); + test( 'Default implementation of startVideoRecording() should throw unimplemented error', () { diff --git a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart index 1e28fa689383..bd516bfdfec1 100644 --- a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart +++ b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart @@ -4,6 +4,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/types/exposure_mode.dart'; +import 'package:camera_platform_interface/src/types/focus_mode.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -11,14 +12,15 @@ void main() { group('CameraInitializedEvent tests', () { test('Constructor should initialize all properties', () { - final event = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final event = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(event.cameraId, 1); expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); + expect(event.focusMode, FocusMode.auto); expect(event.exposurePointSupported, true); + expect(event.focusPointSupported, true); }); test('fromJson should initialize all properties', () { @@ -26,92 +28,101 @@ void main() { 'cameraId': 1, 'previewWidth': 1024.0, 'previewHeight': 640.0, - 'exposureMode': 'auto' + 'exposureMode': 'auto', + 'focusMode': 'auto', + 'exposurePointSupported': true, + 'focusPointSupported': true }); expect(event.cameraId, 1); expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); + expect(event.focusMode, FocusMode.auto); + expect(event.exposurePointSupported, true); + expect(event.focusPointSupported, true); }); test('toJson should return a map with all fields', () { - final event = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final event = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final jsonMap = event.toJson(); - expect(jsonMap.length, 5); + expect(jsonMap.length, 7); expect(jsonMap['cameraId'], 1); expect(jsonMap['previewWidth'], 1024); expect(jsonMap['previewHeight'], 640); expect(jsonMap['exposureMode'], 'auto'); + expect(jsonMap['focusMode'], 'auto'); expect(jsonMap['exposurePointSupported'], true); + expect(jsonMap['focusPointSupported'], true); }); test('equals should return true if objects are the same', () { - final firstEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); - final secondEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); + final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { - final firstEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); - final secondEvent = - CameraInitializedEvent(2, 1024, 640, ExposureMode.auto, true); + final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent(2, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewWidth is different', () { - final firstEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); - final secondEvent = - CameraInitializedEvent(1, 2048, 640, ExposureMode.auto, true); + final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent(1, 2048, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewHeight is different', () { - final firstEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); - final secondEvent = - CameraInitializedEvent(1, 1024, 980, ExposureMode.auto, true); + final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent(1, 1024, 980, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if exposureMode is different', () { - final firstEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); - final secondEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.locked, true); + final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.locked, FocusMode.auto, true, true); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if exposurePointSupported is different', () { + final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, false, true); expect(firstEvent == secondEvent, false); }); - test('equals should return false if exposurePointSupported is different', - () { - final firstEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); - final secondEvent = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, false); + test('equals should return false if focusMode is different', () { + final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.locked, true, true); + + expect(firstEvent == secondEvent, false); + }); + + test('equals should return false if focusPointSupported is different', () { + final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, false); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { - final event = - CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, true); - final expectedHashCode = event.cameraId ^ + final event = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final expectedHashCode = event.cameraId.hashCode ^ event.previewWidth.hashCode ^ event.previewHeight.hashCode ^ event.exposureMode.hashCode ^ - event.exposurePointSupported.hashCode; + event.focusMode.hashCode ^ + event.exposurePointSupported.hashCode ^ + event.focusPointSupported.hashCode; expect(event.hashCode, expectedHashCode); }); @@ -179,9 +190,7 @@ void main() { test('hashCode should match hashCode of all properties', () { final event = CameraResolutionChangedEvent(1, 1024, 640); - final expectedHashCode = event.cameraId.hashCode ^ - event.captureWidth.hashCode ^ - event.captureHeight.hashCode; + final expectedHashCode = event.cameraId.hashCode ^ event.captureWidth.hashCode ^ event.captureHeight.hashCode; expect(event.hashCode, expectedHashCode); }); @@ -242,8 +251,7 @@ void main() { }); test('fromJson should initialize all properties', () { - final event = CameraErrorEvent.fromJson( - {'cameraId': 1, 'description': 'Error'}); + final event = CameraErrorEvent.fromJson({'cameraId': 1, 'description': 'Error'}); expect(event.cameraId, 1); expect(event.description, 'Error'); @@ -282,8 +290,7 @@ void main() { test('hashCode should match hashCode of all properties', () { final event = CameraErrorEvent(1, 'Error'); - final expectedHashCode = - event.cameraId.hashCode ^ event.description.hashCode; + final expectedHashCode = event.cameraId.hashCode ^ event.description.hashCode; expect(event.hashCode, expectedHashCode); }); diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 3413355af9f1..c271dc01f6ae 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -8,6 +8,7 @@ import 'dart:math'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; +import 'package:camera_platform_interface/src/types/focus_mode.dart'; import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -124,6 +125,8 @@ void main() { 1920, 1080, ExposureMode.auto, + FocusMode.auto, + true, true, )); await initializeFuture; @@ -162,6 +165,8 @@ void main() { 1920, 1080, ExposureMode.auto, + FocusMode.auto, + true, true, )); await initializeFuture; @@ -204,6 +209,8 @@ void main() { 1920, 1080, ExposureMode.auto, + FocusMode.auto, + true, true, )); await initializeFuture; @@ -221,6 +228,8 @@ void main() { 3840, 2160, ExposureMode.auto, + FocusMode.auto, + true, true, ); await camera.handleMethodCall( @@ -332,6 +341,8 @@ void main() { 1920, 1080, ExposureMode.auto, + FocusMode.auto, + true, true, ), ); @@ -650,6 +661,54 @@ void main() { ]); }); + test('Should set the focus mode', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusMode': null}, + ); + + // Act + await camera.setFocusMode(cameraId, FocusMode.auto); + await camera.setFocusMode(cameraId, FocusMode.locked); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusMode', + arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setFocusMode', + arguments: {'cameraId': cameraId, 'mode': 'locked'}), + ]); + }); + + test('Should set the exposure point', () async { + // Arrange + MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setFocusPoint': null}, + ); + + // Act + await camera.setFocusPoint(cameraId, Point(0.5, 0.5)); + await camera.setFocusPoint(cameraId, null); + + // Assert + expect(channel.log, [ + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': 0.5, + 'y': 0.5, + 'reset': false + }), + isMethodCall('setFocusPoint', arguments: { + 'cameraId': cameraId, + 'x': null, + 'y': null, + 'reset': true + }), + ]); + }); + test('Should build a texture widget as preview widget', () async { // Act Widget widget = camera.buildPreview(cameraId); diff --git a/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart b/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart new file mode 100644 index 000000000000..ca7ad902820a --- /dev/null +++ b/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart @@ -0,0 +1,31 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_platform_interface/src/types/focus_mode.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('FocusMode should contain 2 options', () { + final values = FocusMode.values; + + expect(values.length, 2); + }); + + test("FocusMode enum should have items in correct index", () { + final values = FocusMode.values; + + expect(values[0], FocusMode.auto); + expect(values[1], FocusMode.locked); + }); + + test("serializeFocusMode() should serialize correctly", () { + expect(serializeFocusMode(FocusMode.auto), "auto"); + expect(serializeFocusMode(FocusMode.locked), "locked"); + }); + + test("deserializeFocusMode() should deserialize correctly", () { + expect(deserializeFocusMode('auto'), FocusMode.auto); + expect(deserializeFocusMode('locked'), FocusMode.locked); + }); +} From deea6a0af1c1558209fd819bb780f548390c259c Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 10:06:36 +0100 Subject: [PATCH 06/31] Formatted code --- .../lib/src/events/camera_event.dart | 16 +++-- .../platform_interface/camera_platform.dart | 1 - .../test/camera_platform_interface_test.dart | 38 +++++----- .../test/events/camera_event_test.dart | 70 +++++++++++++------ 4 files changed, 78 insertions(+), 47 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index 67324ed4635b..e9ca14a92d92 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -32,7 +32,10 @@ abstract class CameraEvent { @override bool operator ==(Object other) => - identical(this, other) || other is CameraEvent && runtimeType == other.runtimeType && cameraId == other.cameraId; + identical(this, other) || + other is CameraEvent && + runtimeType == other.runtimeType && + cameraId == other.cameraId; @override int get hashCode => cameraId.hashCode; @@ -164,7 +167,8 @@ class CameraResolutionChangedEvent extends CameraEvent { captureHeight == other.captureHeight; @override - int get hashCode => super.hashCode ^ captureWidth.hashCode ^ captureHeight.hashCode; + int get hashCode => + super.hashCode ^ captureWidth.hashCode ^ captureHeight.hashCode; } /// An event fired when the camera is going to close. @@ -175,7 +179,8 @@ class CameraClosingEvent extends CameraEvent { /// Converts the supplied [Map] to an instance of the [CameraClosingEvent] /// class. - CameraClosingEvent.fromJson(Map json) : super(json['cameraId']); + CameraClosingEvent.fromJson(Map json) + : super(json['cameraId']); /// Converts the [CameraClosingEvent] instance into a [Map] instance that can /// be serialized to JSON. @@ -185,7 +190,10 @@ class CameraClosingEvent extends CameraEvent { @override bool operator ==(Object other) => - identical(this, other) || super == (other) && other is CameraClosingEvent && runtimeType == other.runtimeType; + identical(this, other) || + super == (other) && + other is CameraClosingEvent && + runtimeType == other.runtimeType; @override int get hashCode => super.hashCode; diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index e40040c76e11..b26ec86bd86a 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -168,7 +168,6 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('setFocusPoint() is not implemented.'); } - /// Gets the maximum supported zoom level for the selected camera. Future getMaxZoomLevel(int cameraId) { throw UnimplementedError('getMaxZoomLevel() is not implemented.'); diff --git a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart index 9aea321ca4c9..80316317e698 100644 --- a/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart +++ b/packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart @@ -266,29 +266,29 @@ void main() { test( 'Default implementation of setFocusMode() should throw unimplemented error', - () { - // Arrange - final cameraPlatform = ExtendsCameraPlatform(); + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); - // Act & Assert - expect( - () => cameraPlatform.setFocusMode(1, null), - throwsUnimplementedError, - ); - }); + // Act & Assert + expect( + () => cameraPlatform.setFocusMode(1, null), + throwsUnimplementedError, + ); + }); test( 'Default implementation of setFocusPoint() should throw unimplemented error', - () { - // Arrange - final cameraPlatform = ExtendsCameraPlatform(); - - // Act & Assert - expect( - () => cameraPlatform.setFocusPoint(1, null), - throwsUnimplementedError, - ); - }); + () { + // Arrange + final cameraPlatform = ExtendsCameraPlatform(); + + // Act & Assert + expect( + () => cameraPlatform.setFocusPoint(1, null), + throwsUnimplementedError, + ); + }); test( 'Default implementation of startVideoRecording() should throw unimplemented error', diff --git a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart index bd516bfdfec1..e68446e135f0 100644 --- a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart +++ b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart @@ -12,7 +12,8 @@ void main() { group('CameraInitializedEvent tests', () { test('Constructor should initialize all properties', () { - final event = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final event = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(event.cameraId, 1); expect(event.previewWidth, 1024); @@ -44,7 +45,8 @@ void main() { }); test('toJson should return a map with all fields', () { - final event = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final event = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final jsonMap = event.toJson(); @@ -59,63 +61,81 @@ void main() { }); test('equals should return true if objects are the same', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); - final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final firstEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); - final secondEvent = CameraInitializedEvent(2, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final firstEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 2, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewWidth is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); - final secondEvent = CameraInitializedEvent(1, 2048, 640, ExposureMode.auto, FocusMode.auto, true, true); + final firstEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 1, 2048, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewHeight is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); - final secondEvent = CameraInitializedEvent(1, 1024, 980, ExposureMode.auto, FocusMode.auto, true, true); + final firstEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 1, 1024, 980, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if exposureMode is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); - final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.locked, FocusMode.auto, true, true); + final firstEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.locked, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); - test('equals should return false if exposurePointSupported is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); - final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, false, true); + test('equals should return false if exposurePointSupported is different', + () { + final firstEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, false, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusMode is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); - final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.locked, true, true); + final firstEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.locked, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusPointSupported is different', () { - final firstEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); - final secondEvent = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, false); + final firstEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, false); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { - final event = CameraInitializedEvent(1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final event = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final expectedHashCode = event.cameraId.hashCode ^ event.previewWidth.hashCode ^ event.previewHeight.hashCode ^ @@ -190,7 +210,9 @@ void main() { test('hashCode should match hashCode of all properties', () { final event = CameraResolutionChangedEvent(1, 1024, 640); - final expectedHashCode = event.cameraId.hashCode ^ event.captureWidth.hashCode ^ event.captureHeight.hashCode; + final expectedHashCode = event.cameraId.hashCode ^ + event.captureWidth.hashCode ^ + event.captureHeight.hashCode; expect(event.hashCode, expectedHashCode); }); @@ -251,7 +273,8 @@ void main() { }); test('fromJson should initialize all properties', () { - final event = CameraErrorEvent.fromJson({'cameraId': 1, 'description': 'Error'}); + final event = CameraErrorEvent.fromJson( + {'cameraId': 1, 'description': 'Error'}); expect(event.cameraId, 1); expect(event.description, 'Error'); @@ -290,7 +313,8 @@ void main() { test('hashCode should match hashCode of all properties', () { final event = CameraErrorEvent(1, 'Error'); - final expectedHashCode = event.cameraId.hashCode ^ event.description.hashCode; + final expectedHashCode = + event.cameraId.hashCode ^ event.description.hashCode; expect(event.hashCode, expectedHashCode); }); From 42a451763ffdf77e9babb5ba3c59b1e6e2e54a41 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 10:08:33 +0100 Subject: [PATCH 07/31] Export focus mode --- .../camera/camera_platform_interface/lib/src/types/types.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart index bab430eb5a69..753c8e4e9053 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/types.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -7,3 +7,4 @@ export 'resolution_preset.dart'; export 'camera_exception.dart'; export 'flash_mode.dart'; export 'exposure_mode.dart'; +export 'focus_mode.dart'; From b595f9317b5cd21c99a2b77f238f6972ad67abc7 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 11:48:14 +0100 Subject: [PATCH 08/31] Add Android and iOS implementations (WIP) --- .../io/flutter/plugins/camera/Camera.java | 1940 +++++++++-------- .../flutter/plugins/camera/DartMessenger.java | 9 +- .../plugins/camera/MethodCallHandlerImpl.java | 32 + .../plugins/camera/types/FocusMode.java | 25 + .../plugins/camera/DartMessengerTest.java | 6 +- .../plugins/camera/types/FocusModeTest.java | 30 + packages/camera/camera/example/lib/main.dart | 96 +- .../camera/camera/ios/Classes/CameraPlugin.m | 118 +- packages/camera/camera/lib/camera.dart | 1 + .../camera/lib/src/camera_controller.dart | 93 +- packages/camera/camera/test/camera_test.dart | 2 + 11 files changed, 1374 insertions(+), 978 deletions(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index e2c8ff3dc1f6..1ac546049aad 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -1,8 +1,5 @@ package io.flutter.plugins.camera; -import static android.view.OrientationEventListener.ORIENTATION_UNKNOWN; -import static io.flutter.plugins.camera.CameraUtils.computeBestPreviewSize; - import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; @@ -38,15 +35,9 @@ import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; + import androidx.annotation.NonNull; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugins.camera.PictureCaptureRequest.State; -import io.flutter.plugins.camera.media.MediaRecorderBuilder; -import io.flutter.plugins.camera.types.ExposureMode; -import io.flutter.plugins.camera.types.FlashMode; -import io.flutter.plugins.camera.types.ResolutionPreset; -import io.flutter.view.TextureRegistry.SurfaceTextureEntry; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -59,943 +50,1024 @@ import java.util.Map; import java.util.concurrent.Executors; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugins.camera.PictureCaptureRequest.State; +import io.flutter.plugins.camera.media.MediaRecorderBuilder; +import io.flutter.plugins.camera.types.ExposureMode; +import io.flutter.plugins.camera.types.FlashMode; +import io.flutter.plugins.camera.types.FocusMode; +import io.flutter.plugins.camera.types.ResolutionPreset; +import io.flutter.view.TextureRegistry.SurfaceTextureEntry; + +import static android.view.OrientationEventListener.ORIENTATION_UNKNOWN; +import static io.flutter.plugins.camera.CameraUtils.computeBestPreviewSize; + public class Camera { - private final SurfaceTextureEntry flutterTexture; - private final CameraManager cameraManager; - private final OrientationEventListener orientationEventListener; - private final boolean isFrontFacing; - private final int sensorOrientation; - private final String cameraName; - private final Size captureSize; - private final Size previewSize; - private final boolean enableAudio; - private final Context applicationContext; - private final CamcorderProfile recordingProfile; - private final DartMessenger dartMessenger; - private final CameraZoom cameraZoom; - - private CameraDevice cameraDevice; - private CameraCaptureSession cameraCaptureSession; - private ImageReader pictureImageReader; - private ImageReader imageStreamReader; - private CaptureRequest.Builder captureRequestBuilder; - private MediaRecorder mediaRecorder; - private boolean recordingVideo; - private File videoRecordingFile; - private int currentOrientation = ORIENTATION_UNKNOWN; - private FlashMode flashMode; - private ExposureMode exposureMode; - private PictureCaptureRequest pictureCaptureRequest; - private MeteringRectangle aeMeteringRectangle; - private int exposureOffset; - - public Camera( - final Activity activity, - final SurfaceTextureEntry flutterTexture, - final DartMessenger dartMessenger, - final String cameraName, - final String resolutionPreset, - final boolean enableAudio) - throws CameraAccessException { - if (activity == null) { - throw new IllegalStateException("No activity available!"); - } - this.cameraName = cameraName; - this.enableAudio = enableAudio; - this.flutterTexture = flutterTexture; - this.dartMessenger = dartMessenger; - this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); - this.applicationContext = activity.getApplicationContext(); - this.flashMode = FlashMode.auto; - this.exposureMode = ExposureMode.auto; - this.exposureOffset = 0; - orientationEventListener = - new OrientationEventListener(activity.getApplicationContext()) { - @Override - public void onOrientationChanged(int i) { - if (i == ORIENTATION_UNKNOWN) { - return; + private final SurfaceTextureEntry flutterTexture; + private final CameraManager cameraManager; + private final OrientationEventListener orientationEventListener; + private final boolean isFrontFacing; + private final int sensorOrientation; + private final String cameraName; + private final Size captureSize; + private final Size previewSize; + private final boolean enableAudio; + private final Context applicationContext; + private final CamcorderProfile recordingProfile; + private final DartMessenger dartMessenger; + private final CameraZoom cameraZoom; + + private CameraDevice cameraDevice; + private CameraCaptureSession cameraCaptureSession; + private ImageReader pictureImageReader; + private ImageReader imageStreamReader; + private CaptureRequest.Builder captureRequestBuilder; + private MediaRecorder mediaRecorder; + private boolean recordingVideo; + private File videoRecordingFile; + private int currentOrientation = ORIENTATION_UNKNOWN; + private FlashMode flashMode; + private ExposureMode exposureMode; + private FocusMode focusMode; + private PictureCaptureRequest pictureCaptureRequest; + private MeteringRectangle aeMeteringRectangle; + private MeteringRectangle afMeteringRectangle; + private int exposureOffset; + + public Camera( + final Activity activity, + final SurfaceTextureEntry flutterTexture, + final DartMessenger dartMessenger, + final String cameraName, + final String resolutionPreset, + final boolean enableAudio) + throws CameraAccessException { + if (activity == null) { + throw new IllegalStateException("No activity available!"); + } + this.cameraName = cameraName; + this.enableAudio = enableAudio; + this.flutterTexture = flutterTexture; + this.dartMessenger = dartMessenger; + this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); + this.applicationContext = activity.getApplicationContext(); + this.flashMode = FlashMode.auto; + this.exposureMode = ExposureMode.auto; + this.focusMode = FocusMode.continuous; + this.exposureOffset = 0; + orientationEventListener = + new OrientationEventListener(activity.getApplicationContext()) { + @Override + public void onOrientationChanged(int i) { + if (i == ORIENTATION_UNKNOWN) { + return; + } + // Convert the raw deg angle to the nearest multiple of 90. + currentOrientation = (int) Math.round(i / 90.0) * 90; + } + }; + orientationEventListener.enable(); + + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); + sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + isFrontFacing = + characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; + ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); + recordingProfile = + CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); + captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); + previewSize = computeBestPreviewSize(cameraName, preset); + cameraZoom = + new CameraZoom( + characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE), + characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)); + } + + private void prepareMediaRecorder(String outputFilePath) throws IOException { + if (mediaRecorder != null) { + mediaRecorder.release(); + } + + mediaRecorder = + new MediaRecorderBuilder(recordingProfile, outputFilePath) + .setEnableAudio(enableAudio) + .setMediaOrientation(getMediaOrientation()) + .build(); + } + + @SuppressLint("MissingPermission") + public void open() throws CameraAccessException { + pictureImageReader = + ImageReader.newInstance( + captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); + + // Used to steam image byte data to dart side. + imageStreamReader = + ImageReader.newInstance( + previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); + + cameraManager.openCamera( + cameraName, + new CameraDevice.StateCallback() { + @Override + public void onOpened(@NonNull CameraDevice device) { + cameraDevice = device; + try { + startPreview(); + dartMessenger.sendCameraInitializedEvent( + previewSize.getWidth(), + previewSize.getHeight(), + exposureMode, + focusMode, + isExposurePointSupported(), + isFocusPointSupported()); + } catch (CameraAccessException e) { + dartMessenger.sendCameraErrorEvent(e.getMessage()); + close(); + } + } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + dartMessenger.sendCameraClosingEvent(); + super.onClosed(camera); + } + + @Override + public void onDisconnected(@NonNull CameraDevice cameraDevice) { + close(); + dartMessenger.sendCameraErrorEvent("The camera was disconnected."); + } + + @Override + public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { + close(); + String errorDescription; + switch (errorCode) { + case ERROR_CAMERA_IN_USE: + errorDescription = "The camera device is in use already."; + break; + case ERROR_MAX_CAMERAS_IN_USE: + errorDescription = "Max cameras in use"; + break; + case ERROR_CAMERA_DISABLED: + errorDescription = "The camera device could not be opened due to a device policy."; + break; + case ERROR_CAMERA_DEVICE: + errorDescription = "The camera device has encountered a fatal error"; + break; + case ERROR_CAMERA_SERVICE: + errorDescription = "The camera service has encountered a fatal error."; + break; + default: + errorDescription = "Unknown camera error"; + } + dartMessenger.sendCameraErrorEvent(errorDescription); + } + }, + null); + } + + private void writeToFile(ByteBuffer buffer, File file) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream(file)) { + while (0 < buffer.remaining()) { + outputStream.getChannel().write(buffer); } - // Convert the raw deg angle to the nearest multiple of 90. - currentOrientation = (int) Math.round(i / 90.0) * 90; - } - }; - orientationEventListener.enable(); - - CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); - sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - isFrontFacing = - characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; - ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); - recordingProfile = - CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); - captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight); - previewSize = computeBestPreviewSize(cameraName, preset); - cameraZoom = - new CameraZoom( - characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE), - characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)); - } - - private void prepareMediaRecorder(String outputFilePath) throws IOException { - if (mediaRecorder != null) { - mediaRecorder.release(); - } - - mediaRecorder = - new MediaRecorderBuilder(recordingProfile, outputFilePath) - .setEnableAudio(enableAudio) - .setMediaOrientation(getMediaOrientation()) - .build(); - } - - @SuppressLint("MissingPermission") - public void open() throws CameraAccessException { - pictureImageReader = - ImageReader.newInstance( - captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, 2); - - // Used to steam image byte data to dart side. - imageStreamReader = - ImageReader.newInstance( - previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2); - - cameraManager.openCamera( - cameraName, - new CameraDevice.StateCallback() { - @Override - public void onOpened(@NonNull CameraDevice device) { - cameraDevice = device; - try { - startPreview(); - dartMessenger.sendCameraInitializedEvent( - previewSize.getWidth(), - previewSize.getHeight(), - exposureMode, - isExposurePointSupported()); - } catch (CameraAccessException e) { - dartMessenger.sendCameraErrorEvent(e.getMessage()); - close(); + } + } + + public void takePicture(@NonNull final Result result) { + // Only take 1 picture at a time + if (pictureCaptureRequest != null && !pictureCaptureRequest.isFinished()) { + result.error("captureAlreadyActive", "Picture is currently already being captured", null); + return; + } + // Store the result + this.pictureCaptureRequest = new PictureCaptureRequest(result); + + // Create temporary file + final File outputDir = applicationContext.getCacheDir(); + final File file; + try { + file = File.createTempFile("CAP", ".jpg", outputDir); + } catch (IOException | SecurityException e) { + pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null); + return; + } + + // Listen for picture being taken + pictureImageReader.setOnImageAvailableListener( + reader -> { + try (Image image = reader.acquireLatestImage()) { + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + writeToFile(buffer, file); + pictureCaptureRequest.finish(file.getAbsolutePath()); + } catch (IOException e) { + pictureCaptureRequest.error("IOError", "Failed saving image", null); + } + }, + null); + + runPictureAutoFocus(); + } + + private final CameraCaptureSession.CaptureCallback pictureCaptureCallback = + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + processCapture(result); + } + + @Override + public void onCaptureProgressed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureResult partialResult) { + processCapture(partialResult); + } + + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + assert (pictureCaptureRequest != null); + String reason; + switch (failure.getReason()) { + case CaptureFailure.REASON_ERROR: + reason = "An error happened in the framework"; + break; + case CaptureFailure.REASON_FLUSHED: + reason = "The capture has failed due to an abortCaptures() call"; + break; + default: + reason = "Unknown reason"; + } + pictureCaptureRequest.error("captureFailure", reason, null); + } + + private void processCapture(CaptureResult result) { + if (pictureCaptureRequest == null) { + return; + } + + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + switch (pictureCaptureRequest.getState()) { + case focusing: + if (afState == null) { + return; + } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED + || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { + // Some devices might return null here, in which case we will also continue. + if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { + runPictureCapture(); + } else { + runPicturePreCapture(); + } + } + break; + case preCapture: + // Some devices might return null here, in which case we will also continue. + if (aeState == null + || aeState == CaptureRequest.CONTROL_AE_STATE_PRECAPTURE + || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED + || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { + pictureCaptureRequest.setState(State.waitingPreCaptureReady); + } + break; + case waitingPreCaptureReady: + if (aeState == null || aeState != CaptureRequest.CONTROL_AE_STATE_PRECAPTURE) { + runPictureCapture(); + } + } + } + }; + + private void runPictureAutoFocus() { + assert (pictureCaptureRequest != null); + pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing); + lockAutoFocus(); + } + + private void runPicturePreCapture() { + assert (pictureCaptureRequest != null); + pictureCaptureRequest.setState(PictureCaptureRequest.State.preCapture); + + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + try { + cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null); + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + private void runPictureCapture() { + assert (pictureCaptureRequest != null); + pictureCaptureRequest.setState(PictureCaptureRequest.State.capturing); + try { + final CaptureRequest.Builder captureBuilder = + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + captureBuilder.addTarget(pictureImageReader.getSurface()); + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); + switch (flashMode) { + case off: + captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); + break; + case auto: + captureBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + break; + case always: + default: + captureBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + break; } - } - - @Override - public void onClosed(@NonNull CameraDevice camera) { - dartMessenger.sendCameraClosingEvent(); - super.onClosed(camera); - } - - @Override - public void onDisconnected(@NonNull CameraDevice cameraDevice) { - close(); - dartMessenger.sendCameraErrorEvent("The camera was disconnected."); - } - - @Override - public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { - close(); - String errorDescription; - switch (errorCode) { - case ERROR_CAMERA_IN_USE: - errorDescription = "The camera device is in use already."; + cameraCaptureSession.stopRepeating(); + cameraCaptureSession.capture( + captureBuilder.build(), + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + unlockAutoFocus(); + } + }, + null); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + private void lockAutoFocus() { + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); + try { + cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + private void unlockAutoFocus() { + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); + initPreviewCaptureBuilder(); + try { + cameraCaptureSession.capture(captureRequestBuilder.build(), null, null); + } catch (CameraAccessException ignored) { + } + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); + try { + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + private void createCaptureSession(int templateType, Surface... surfaces) + throws CameraAccessException { + createCaptureSession(templateType, null, surfaces); + } + + private void createCaptureSession( + int templateType, Runnable onSuccessCallback, Surface... surfaces) + throws CameraAccessException { + // Close any existing capture session. + closeCaptureSession(); + + // Create a new capture builder. + captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); + + // Build Flutter surface to render to + SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); + surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); + Surface flutterSurface = new Surface(surfaceTexture); + captureRequestBuilder.addTarget(flutterSurface); + + List remainingSurfaces = Arrays.asList(surfaces); + if (templateType != CameraDevice.TEMPLATE_PREVIEW) { + // If it is not preview mode, add all surfaces as targets. + for (Surface surface : remainingSurfaces) { + captureRequestBuilder.addTarget(surface); + } + } + + // Prepare the callback + CameraCaptureSession.StateCallback callback = + new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + try { + if (cameraDevice == null) { + dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); + return; + } + cameraCaptureSession = session; + initPreviewCaptureBuilder(); + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), + pictureCaptureCallback, + new Handler(Looper.getMainLooper())); + if (onSuccessCallback != null) { + onSuccessCallback.run(); + } + } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { + dartMessenger.sendCameraErrorEvent(e.getMessage()); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { + dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); + } + }; + + // Start the session + if (VERSION.SDK_INT >= VERSION_CODES.P) { + // Collect all surfaces we want to render to. + List configs = new ArrayList<>(); + configs.add(new OutputConfiguration(flutterSurface)); + for (Surface surface : remainingSurfaces) { + configs.add(new OutputConfiguration(surface)); + } + createCaptureSessionWithSessionConfig(configs, callback); + } else { + // Collect all surfaces we want to render to. + List surfaceList = new ArrayList<>(); + surfaceList.add(flutterSurface); + surfaceList.addAll(remainingSurfaces); + createCaptureSession(surfaceList, callback); + } + } + + @TargetApi(VERSION_CODES.P) + private void createCaptureSessionWithSessionConfig( + List outputConfigs, CameraCaptureSession.StateCallback callback) + throws CameraAccessException { + cameraDevice.createCaptureSession( + new SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, + outputConfigs, + Executors.newSingleThreadExecutor(), + callback)); + } + + @TargetApi(VERSION_CODES.LOLLIPOP) + @SuppressWarnings("deprecation") + private void createCaptureSession( + List surfaces, CameraCaptureSession.StateCallback callback) + throws CameraAccessException { + cameraDevice.createCaptureSession(surfaces, callback, null); + } + + public void startVideoRecording(Result result) { + final File outputDir = applicationContext.getCacheDir(); + try { + videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); + } catch (IOException | SecurityException e) { + result.error("cannotCreateFile", e.getMessage(), null); + return; + } + + try { + prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); + recordingVideo = true; + createCaptureSession( + CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); + result.success(null); + } catch (CameraAccessException | IOException e) { + recordingVideo = false; + videoRecordingFile = null; + result.error("videoRecordingFailed", e.getMessage(), null); + } + } + + public void stopVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + recordingVideo = false; + mediaRecorder.stop(); + mediaRecorder.reset(); + startPreview(); + result.success(videoRecordingFile.getAbsolutePath()); + videoRecordingFile = null; + } catch (CameraAccessException | IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + } + } + + public void pauseVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.pause(); + } else { + result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; + } + + result.success(null); + } + + public void resumeVideoRecording(@NonNull final Result result) { + if (!recordingVideo) { + result.success(null); + return; + } + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mediaRecorder.resume(); + } else { + result.error( + "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); + return; + } + } catch (IllegalStateException e) { + result.error("videoRecordingFailed", e.getMessage(), null); + return; + } + + result.success(null); + } + + public void setFlashMode(@NonNull final Result result, FlashMode mode) + throws CameraAccessException { + // Get the flash availability + Boolean flashAvailable = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + + // Check if flash is available. + if (flashAvailable == null || !flashAvailable) { + result.error("setFlashModeFailed", "Device does not have flash capabilities", null); + return; + } + + // If switching directly from torch to auto or on, make sure we turn off the torch. + if (flashMode == FlashMode.torch && mode != FlashMode.torch && mode != FlashMode.off) { + this.flashMode = FlashMode.off; + initPreviewCaptureBuilder(); + this.cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), + new CaptureCallback() { + private boolean isFinished = false; + + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult captureResult) { + if (isFinished) { + return; + } + + updateFlash(mode); + result.success(null); + isFinished = true; + } + + @Override + public void onCaptureFailed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureFailure failure) { + if (isFinished) { + return; + } + + result.error("setFlashModeFailed", "Could not set flash mode.", null); + isFinished = true; + } + }, + null); + } else { + updateFlash(mode); + result.success(null); + } + } + + private void updateFlash(FlashMode mode) { + // Get flash + flashMode = mode; + initPreviewCaptureBuilder(); + try { + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + + public void setExposureMode(@NonNull final Result result, ExposureMode mode) + throws CameraAccessException { + this.exposureMode = mode; + initPreviewCaptureBuilder(); + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + result.success(null); + } + + public void setExposurePoint(@NonNull final Result result, Double x, Double y) + throws CameraAccessException { + // Check if exposure point functionality is available. + if (!isExposurePointSupported()) { + result.error( + "setExposurePointFailed", "Device does not have exposure point capabilities", null); + return; + } + // Check if we are doing a reset or not + if (x == null || y == null) { + x = 0.5; + y = 0.5; + } + // Get the current region boundaries. + Size maxBoundaries = getRegionBoundaries(); + if (maxBoundaries == null) { + result.error("setExposurePointFailed", "Could not determine max region boundaries", null); + return; + } + // Set the metering rectangle + aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); + // Apply it + initPreviewCaptureBuilder(); + this.cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + result.success(null); + } + + public void setFocusMode(@NonNull final Result result, FocusMode mode) + throws CameraAccessException { + this.focusMode = mode; + initPreviewCaptureBuilder(); + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + result.success(null); + } + + public void setFocusPoint(@NonNull final Result result, Double x, Double y) + throws CameraAccessException { + // Check if focus point functionality is available. + if (!isFocusPointSupported()) { + result.error("setFocusPointFailed", "Device does not have focus point capabilities", null); + return; + } + // Check if we are doing a reset or not + if (x == null || y == null) { + x = 0.5; + y = 0.5; + } + // Get the current region boundaries. + Size maxBoundaries = getRegionBoundaries(); + if (maxBoundaries == null) { + result.error("setFocusPointFailed", "Could not determine max region boundaries", null); + return; + } + // Set the metering rectangle + afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); + // Apply it + initPreviewCaptureBuilder(); + this.cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + result.success(null); + } + + private MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) { + // Interpolate the target coordinate + int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1))); + int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1))); + // Determine the dimensions of the metering triangle (1th of the viewport) + int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d); + int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d); + // Adjust target coordinate to represent top-left corner of metering rectangle + targetX -= targetWidth / 2; + targetY -= targetHeight / 2; + // Adjust target coordinate as to not fall out of bounds + if (targetX < 0) targetX = 0; + if (targetY < 0) targetY = 0; + int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth; + int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight; + if (targetX > maxTargetX) targetX = maxTargetX; + if (targetY > maxTargetY) targetY = maxTargetY; + // Build the metering rectangle + return new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1); + } + + @SuppressLint("NewApi") + private Size getRegionBoundaries() throws CameraAccessException { + // Check if the device supports distortion correction + boolean supportsDistortionCorrection = false; + if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.P) { + int[] availableDistortionCorrectionModes = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); + if (availableDistortionCorrectionModes == null) + availableDistortionCorrectionModes = new int[0]; + long nonOffModesSupported = + Arrays.stream(availableDistortionCorrectionModes) + .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) + .count(); + supportsDistortionCorrection = nonOffModesSupported > 0; + } + // No distortion correction support + if (!supportsDistortionCorrection) { + return cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); + } + // Get the current distortion correction mode + Integer distortionCorrectionMode = + captureRequestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); + // Return the correct boundaries depending on the mode + android.graphics.Rect rect; + if (distortionCorrectionMode == null + || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) { + rect = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); + } else { + rect = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + } + return rect == null ? null : new Size(rect.width(), rect.height()); + } + + private boolean isExposurePointSupported() throws CameraAccessException { + Integer supportedRegions = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); + return supportedRegions != null && supportedRegions > 0; + } + + private boolean isFocusPointSupported() throws CameraAccessException { + Integer supportedRegions = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); + return supportedRegions != null && supportedRegions > 0; + } + + public double getMinExposureOffset() throws CameraAccessException { + Range range = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + double minStepped = range == null ? 0 : range.getLower(); + double stepSize = getExposureOffsetStepSize(); + return minStepped * stepSize; + } + + public double getMaxExposureOffset() throws CameraAccessException { + Range range = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + double maxStepped = range == null ? 0 : range.getUpper(); + double stepSize = getExposureOffsetStepSize(); + return maxStepped * stepSize; + } + + public double getExposureOffsetStepSize() throws CameraAccessException { + Rational stepSize = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); + return stepSize == null ? 0.0 : stepSize.doubleValue(); + } + + public void setExposureOffset(@NonNull final Result result, double offset) + throws CameraAccessException { + // Set the exposure offset + double stepSize = getExposureOffsetStepSize(); + exposureOffset = (int) (offset / stepSize); + // Apply it + initPreviewCaptureBuilder(); + this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + result.success(offset); + } + + private void initPreviewCaptureBuilder() { + captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); + // Applying flash modes + switch (flashMode) { + case off: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); break; - case ERROR_MAX_CAMERAS_IN_USE: - errorDescription = "Max cameras in use"; + case auto: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); break; - case ERROR_CAMERA_DISABLED: - errorDescription = "The camera device could not be opened due to a device policy."; + case always: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); break; - case ERROR_CAMERA_DEVICE: - errorDescription = "The camera device has encountered a fatal error"; + case torch: + default: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); break; - case ERROR_CAMERA_SERVICE: - errorDescription = "The camera service has encountered a fatal error."; + } + // Applying auto exposure + if (aeMeteringRectangle != null) { + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{aeMeteringRectangle}); + } + switch (exposureMode) { + case locked: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); break; - default: - errorDescription = "Unknown camera error"; - } - dartMessenger.sendCameraErrorEvent(errorDescription); - } - }, - null); - } - - private void writeToFile(ByteBuffer buffer, File file) throws IOException { - try (FileOutputStream outputStream = new FileOutputStream(file)) { - while (0 < buffer.remaining()) { - outputStream.getChannel().write(buffer); - } - } - } - - public void takePicture(@NonNull final Result result) { - // Only take 1 picture at a time - if (pictureCaptureRequest != null && !pictureCaptureRequest.isFinished()) { - result.error("captureAlreadyActive", "Picture is currently already being captured", null); - return; - } - // Store the result - this.pictureCaptureRequest = new PictureCaptureRequest(result); - - // Create temporary file - final File outputDir = applicationContext.getCacheDir(); - final File file; - try { - file = File.createTempFile("CAP", ".jpg", outputDir); - } catch (IOException | SecurityException e) { - pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null); - return; - } - - // Listen for picture being taken - pictureImageReader.setOnImageAvailableListener( - reader -> { - try (Image image = reader.acquireLatestImage()) { - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - writeToFile(buffer, file); - pictureCaptureRequest.finish(file.getAbsolutePath()); - } catch (IOException e) { - pictureCaptureRequest.error("IOError", "Failed saving image", null); - } - }, - null); - - runPictureAutoFocus(); - } - - private final CameraCaptureSession.CaptureCallback pictureCaptureCallback = - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull TotalCaptureResult result) { - processCapture(result); - } - - @Override - public void onCaptureProgressed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureResult partialResult) { - processCapture(partialResult); - } - - @Override - public void onCaptureFailed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureFailure failure) { - assert (pictureCaptureRequest != null); - String reason; - switch (failure.getReason()) { - case CaptureFailure.REASON_ERROR: - reason = "An error happened in the framework"; - break; - case CaptureFailure.REASON_FLUSHED: - reason = "The capture has failed due to an abortCaptures() call"; - break; + case auto: default: - reason = "Unknown reason"; - } - pictureCaptureRequest.error("captureFailure", reason, null); + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); + break; } + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureOffset); + // Applying auto focus + switch (focusMode) { + case auto: + captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); + break; + case continuous: + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_MODE, recordingVideo ? CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO : CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + default: + break; + } + if (afMeteringRectangle != null) { + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{afMeteringRectangle}); + } + } - private void processCapture(CaptureResult result) { - if (pictureCaptureRequest == null) { + public void startPreview() throws CameraAccessException { + if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; + + createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); + } + + public void startPreviewWithImageStream(EventChannel imageStreamChannel) + throws CameraAccessException { + createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); + + imageStreamChannel.setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object o, EventChannel.EventSink imageStreamSink) { + setImageStreamImageAvailableListener(imageStreamSink); + } + + @Override + public void onCancel(Object o) { + imageStreamReader.setOnImageAvailableListener(null, null); + } + }); + } + + private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { + imageStreamReader.setOnImageAvailableListener( + reader -> { + Image img = reader.acquireLatestImage(); + if (img == null) return; + + List> planes = new ArrayList<>(); + for (Image.Plane plane : img.getPlanes()) { + ByteBuffer buffer = plane.getBuffer(); + + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes, 0, bytes.length); + + Map planeBuffer = new HashMap<>(); + planeBuffer.put("bytesPerRow", plane.getRowStride()); + planeBuffer.put("bytesPerPixel", plane.getPixelStride()); + planeBuffer.put("bytes", bytes); + + planes.add(planeBuffer); + } + + Map imageBuffer = new HashMap<>(); + imageBuffer.put("width", img.getWidth()); + imageBuffer.put("height", img.getHeight()); + imageBuffer.put("format", img.getFormat()); + imageBuffer.put("planes", planes); + + imageStreamSink.success(imageBuffer); + img.close(); + }, + null); + } + + public float getMaxZoomLevel() { + return cameraZoom.maxZoom; + } + + public float getMinZoomLevel() { + return CameraZoom.DEFAULT_ZOOM_FACTOR; + } + + public void setZoomLevel(@NonNull final Result result, float zoom) throws CameraAccessException { + float maxZoom = cameraZoom.maxZoom; + float minZoom = CameraZoom.DEFAULT_ZOOM_FACTOR; + + if (zoom > maxZoom || zoom < minZoom) { + String errorMessage = + String.format( + Locale.ENGLISH, + "Zoom level out of bounds (zoom level should be between %f and %f).", + minZoom, + maxZoom); + result.error("ZOOM_ERROR", errorMessage, null); return; - } + } - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); - switch (pictureCaptureRequest.getState()) { - case focusing: - if (afState == null) { - return; - } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED - || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { - // Some devices might return null here, in which case we will also continue. - if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { - runPictureCapture(); - } else { - runPicturePreCapture(); - } - } - break; - case preCapture: - // Some devices might return null here, in which case we will also continue. - if (aeState == null - || aeState == CaptureRequest.CONTROL_AE_STATE_PRECAPTURE - || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED - || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { - pictureCaptureRequest.setState(State.waitingPreCaptureReady); - } - break; - case waitingPreCaptureReady: - if (aeState == null || aeState != CaptureRequest.CONTROL_AE_STATE_PRECAPTURE) { - runPictureCapture(); - } - } - } - }; - - private void runPictureAutoFocus() { - assert (pictureCaptureRequest != null); - pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing); - lockAutoFocus(); - } - - private void runPicturePreCapture() { - assert (pictureCaptureRequest != null); - pictureCaptureRequest.setState(PictureCaptureRequest.State.preCapture); - - captureRequestBuilder.set( - CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, - CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); - try { - cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null); - captureRequestBuilder.set( - CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, - CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); - } catch (CameraAccessException e) { - pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); - } - } - - private void runPictureCapture() { - assert (pictureCaptureRequest != null); - pictureCaptureRequest.setState(PictureCaptureRequest.State.capturing); - try { - final CaptureRequest.Builder captureBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - captureBuilder.addTarget(pictureImageReader.getSurface()); - captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); - switch (flashMode) { - case off: - captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); - captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); - break; - case auto: - captureBuilder.set( - CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); - break; - case always: - default: - captureBuilder.set( - CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); - break; - } - cameraCaptureSession.stopRepeating(); - cameraCaptureSession.capture( - captureBuilder.build(), - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull TotalCaptureResult result) { - unlockAutoFocus(); - } - }, - null); - } catch (CameraAccessException e) { - pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); - } - } - - private void lockAutoFocus() { - captureRequestBuilder.set( - CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); - try { - cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null); - } catch (CameraAccessException e) { - pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); - } - } - - private void unlockAutoFocus() { - captureRequestBuilder.set( - CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); - initPreviewCaptureBuilder(); - try { - cameraCaptureSession.capture(captureRequestBuilder.build(), null, null); - } catch (CameraAccessException ignored) { - } - captureRequestBuilder.set( - CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); - try { - cameraCaptureSession.setRepeatingRequest( - captureRequestBuilder.build(), pictureCaptureCallback, null); - } catch (CameraAccessException e) { - pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); - } - } - - private void createCaptureSession(int templateType, Surface... surfaces) - throws CameraAccessException { - createCaptureSession(templateType, null, surfaces); - } - - private void createCaptureSession( - int templateType, Runnable onSuccessCallback, Surface... surfaces) - throws CameraAccessException { - // Close any existing capture session. - closeCaptureSession(); - - // Create a new capture builder. - captureRequestBuilder = cameraDevice.createCaptureRequest(templateType); - - // Build Flutter surface to render to - SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); - surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); - Surface flutterSurface = new Surface(surfaceTexture); - captureRequestBuilder.addTarget(flutterSurface); - - List remainingSurfaces = Arrays.asList(surfaces); - if (templateType != CameraDevice.TEMPLATE_PREVIEW) { - // If it is not preview mode, add all surfaces as targets. - for (Surface surface : remainingSurfaces) { - captureRequestBuilder.addTarget(surface); - } - } - - // Prepare the callback - CameraCaptureSession.StateCallback callback = - new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession session) { - try { - if (cameraDevice == null) { - dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); - return; - } - cameraCaptureSession = session; - initPreviewCaptureBuilder(); - cameraCaptureSession.setRepeatingRequest( - captureRequestBuilder.build(), - pictureCaptureCallback, - new Handler(Looper.getMainLooper())); - if (onSuccessCallback != null) { - onSuccessCallback.run(); - } - } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { - dartMessenger.sendCameraErrorEvent(e.getMessage()); - } - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); - } - }; - - // Start the session - if (VERSION.SDK_INT >= VERSION_CODES.P) { - // Collect all surfaces we want to render to. - List configs = new ArrayList<>(); - configs.add(new OutputConfiguration(flutterSurface)); - for (Surface surface : remainingSurfaces) { - configs.add(new OutputConfiguration(surface)); - } - createCaptureSessionWithSessionConfig(configs, callback); - } else { - // Collect all surfaces we want to render to. - List surfaceList = new ArrayList<>(); - surfaceList.add(flutterSurface); - surfaceList.addAll(remainingSurfaces); - createCaptureSession(surfaceList, callback); - } - } - - @TargetApi(VERSION_CODES.P) - private void createCaptureSessionWithSessionConfig( - List outputConfigs, CameraCaptureSession.StateCallback callback) - throws CameraAccessException { - cameraDevice.createCaptureSession( - new SessionConfiguration( - SessionConfiguration.SESSION_REGULAR, - outputConfigs, - Executors.newSingleThreadExecutor(), - callback)); - } - - @TargetApi(VERSION_CODES.LOLLIPOP) - @SuppressWarnings("deprecation") - private void createCaptureSession( - List surfaces, CameraCaptureSession.StateCallback callback) - throws CameraAccessException { - cameraDevice.createCaptureSession(surfaces, callback, null); - } - - public void startVideoRecording(Result result) { - final File outputDir = applicationContext.getCacheDir(); - try { - videoRecordingFile = File.createTempFile("REC", ".mp4", outputDir); - } catch (IOException | SecurityException e) { - result.error("cannotCreateFile", e.getMessage(), null); - return; - } - - try { - prepareMediaRecorder(videoRecordingFile.getAbsolutePath()); - recordingVideo = true; - createCaptureSession( - CameraDevice.TEMPLATE_RECORD, () -> mediaRecorder.start(), mediaRecorder.getSurface()); - result.success(null); - } catch (CameraAccessException | IOException e) { - recordingVideo = false; - videoRecordingFile = null; - result.error("videoRecordingFailed", e.getMessage(), null); - } - } - - public void stopVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - recordingVideo = false; - mediaRecorder.stop(); - mediaRecorder.reset(); - startPreview(); - result.success(videoRecordingFile.getAbsolutePath()); - videoRecordingFile = null; - } catch (CameraAccessException | IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - } - } - - public void pauseVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.pause(); - } else { - result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; - } - - result.success(null); - } - - public void resumeVideoRecording(@NonNull final Result result) { - if (!recordingVideo) { - result.success(null); - return; - } - - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mediaRecorder.resume(); - } else { - result.error( - "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); - return; - } - } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; - } - - result.success(null); - } - - public void setFlashMode(@NonNull final Result result, FlashMode mode) - throws CameraAccessException { - // Get the flash availability - Boolean flashAvailable = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.FLASH_INFO_AVAILABLE); - - // Check if flash is available. - if (flashAvailable == null || !flashAvailable) { - result.error("setFlashModeFailed", "Device does not have flash capabilities", null); - return; - } - - // If switching directly from torch to auto or on, make sure we turn off the torch. - if (flashMode == FlashMode.torch && mode != FlashMode.torch && mode != FlashMode.off) { - this.flashMode = FlashMode.off; - initPreviewCaptureBuilder(); - this.cameraCaptureSession.setRepeatingRequest( - captureRequestBuilder.build(), - new CaptureCallback() { - private boolean isFinished = false; - - @Override - public void onCaptureCompleted( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull TotalCaptureResult captureResult) { - if (isFinished) { - return; - } + //Zoom area is calculated relative to sensor area (activeRect) + if (captureRequestBuilder != null) { + final Rect computedZoom = cameraZoom.computeZoom(zoom); + captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom); + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + } - updateFlash(mode); - result.success(null); - isFinished = true; - } + result.success(null); + } - @Override - public void onCaptureFailed( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull CaptureFailure failure) { - if (isFinished) { - return; - } + private void closeCaptureSession() { + if (cameraCaptureSession != null) { + cameraCaptureSession.close(); + cameraCaptureSession = null; + } + } - result.error("setFlashModeFailed", "Could not set flash mode.", null); - isFinished = true; - } - }, - null); - } else { - updateFlash(mode); - result.success(null); - } - } - - private void updateFlash(FlashMode mode) { - // Get flash - flashMode = mode; - initPreviewCaptureBuilder(); - try { - cameraCaptureSession.setRepeatingRequest( - captureRequestBuilder.build(), pictureCaptureCallback, null); - } catch (CameraAccessException e) { - pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); - } - } - - public void setExposureMode(@NonNull final Result result, ExposureMode mode) - throws CameraAccessException { - this.exposureMode = mode; - initPreviewCaptureBuilder(); - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - result.success(null); - } - - public void setExposurePoint(@NonNull final Result result, Double x, Double y) - throws CameraAccessException { - // Check if exposure point functionality is available. - if (!isExposurePointSupported()) { - result.error( - "setExposurePointFailed", "Device does not have exposure point capabilities", null); - return; - } - // Check if we are doing a reset or not - if (x == null || y == null) { - x = 0.5; - y = 0.5; - } - // Get the current exposure point boundaries. - Size maxBoundaries = getExposureBoundaries(); - if (maxBoundaries == null) { - result.error("setExposurePointFailed", "Could not determine max region boundaries", null); - return; - } - // Interpolate the target coordinate - int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1))); - int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1))); - // Determine the dimensions of the metering triangle (1th of the viewport) - int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d); - int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d); - // Adjust target coordinate to represent top-left corner of metering rectangle - targetX -= targetWidth / 2; - targetY -= targetHeight / 2; - // Adjust target coordinate as to not fall out of bounds - if (targetX < 0) targetX = 0; - if (targetY < 0) targetY = 0; - int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth; - int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight; - if (targetX > maxTargetX) targetX = maxTargetX; - if (targetY > maxTargetY) targetY = maxTargetY; - // Set the metering rectangle - aeMeteringRectangle = new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1); - // Apply it - initPreviewCaptureBuilder(); - this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - result.success(null); - } - - @SuppressLint("NewApi") - private Size getExposureBoundaries() throws CameraAccessException { - // Check if the device supports distortion correction - boolean supportsDistortionCorrection = false; - if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.P) { - int[] availableDistortionCorrectionModes = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); - if (availableDistortionCorrectionModes == null) - availableDistortionCorrectionModes = new int[0]; - long nonOffModesSupported = - Arrays.stream(availableDistortionCorrectionModes) - .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) - .count(); - supportsDistortionCorrection = nonOffModesSupported > 0; - } - // No distortion correction support - if (!supportsDistortionCorrection) { - return cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); - } - // Get the current distortion correction mode - Integer distortionCorrectionMode = - captureRequestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); - // Return the correct boundaries depending on the mode - android.graphics.Rect rect; - if (distortionCorrectionMode == null - || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) { - rect = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); - } else { - rect = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - } - return rect == null ? null : new Size(rect.width(), rect.height()); - } - - private boolean isExposurePointSupported() throws CameraAccessException { - Integer supportedRegions = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); - return supportedRegions != null && supportedRegions > 0; - } - - public double getMinExposureOffset() throws CameraAccessException { - Range range = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); - double minStepped = range == null ? 0 : range.getLower(); - double stepSize = getExposureOffsetStepSize(); - return minStepped * stepSize; - } - - public double getMaxExposureOffset() throws CameraAccessException { - Range range = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); - double maxStepped = range == null ? 0 : range.getUpper(); - double stepSize = getExposureOffsetStepSize(); - return maxStepped * stepSize; - } - - public double getExposureOffsetStepSize() throws CameraAccessException { - Rational stepSize = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); - return stepSize == null ? 0.0 : stepSize.doubleValue(); - } - - public void setExposureOffset(@NonNull final Result result, double offset) - throws CameraAccessException { - // Set the exposure offset - double stepSize = getExposureOffsetStepSize(); - exposureOffset = (int) (offset / stepSize); - // Apply it - initPreviewCaptureBuilder(); - this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - result.success(offset); - } - - private void initPreviewCaptureBuilder() { - captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); - // Applying flash modes - switch (flashMode) { - case off: - captureRequestBuilder.set( - CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); - captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); - break; - case auto: - captureRequestBuilder.set( - CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); - captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); - break; - case always: - captureRequestBuilder.set( - CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); - captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); - break; - case torch: - default: - captureRequestBuilder.set( - CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); - captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); - break; - } - // Applying auto exposure - if (aeMeteringRectangle != null) { - captureRequestBuilder.set( - CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {aeMeteringRectangle}); - } - switch (exposureMode) { - case locked: - captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); - break; - case auto: - default: - captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); - break; - } - captureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureOffset); - // Applying auto focus - captureRequestBuilder.set( - CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); - } - - public void startPreview() throws CameraAccessException { - if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; - - createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); - } - - public void startPreviewWithImageStream(EventChannel imageStreamChannel) - throws CameraAccessException { - createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); - - imageStreamChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object o, EventChannel.EventSink imageStreamSink) { - setImageStreamImageAvailableListener(imageStreamSink); - } - - @Override - public void onCancel(Object o) { - imageStreamReader.setOnImageAvailableListener(null, null); - } - }); - } - - private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { - imageStreamReader.setOnImageAvailableListener( - reader -> { - Image img = reader.acquireLatestImage(); - if (img == null) return; - - List> planes = new ArrayList<>(); - for (Image.Plane plane : img.getPlanes()) { - ByteBuffer buffer = plane.getBuffer(); - - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes, 0, bytes.length); - - Map planeBuffer = new HashMap<>(); - planeBuffer.put("bytesPerRow", plane.getRowStride()); - planeBuffer.put("bytesPerPixel", plane.getPixelStride()); - planeBuffer.put("bytes", bytes); - - planes.add(planeBuffer); - } - - Map imageBuffer = new HashMap<>(); - imageBuffer.put("width", img.getWidth()); - imageBuffer.put("height", img.getHeight()); - imageBuffer.put("format", img.getFormat()); - imageBuffer.put("planes", planes); - - imageStreamSink.success(imageBuffer); - img.close(); - }, - null); - } - - public float getMaxZoomLevel() { - return cameraZoom.maxZoom; - } - - public float getMinZoomLevel() { - return CameraZoom.DEFAULT_ZOOM_FACTOR; - } - - public void setZoomLevel(@NonNull final Result result, float zoom) throws CameraAccessException { - float maxZoom = cameraZoom.maxZoom; - float minZoom = CameraZoom.DEFAULT_ZOOM_FACTOR; - - if (zoom > maxZoom || zoom < minZoom) { - String errorMessage = - String.format( - Locale.ENGLISH, - "Zoom level out of bounds (zoom level should be between %f and %f).", - minZoom, - maxZoom); - result.error("ZOOM_ERROR", errorMessage, null); - return; - } - - //Zoom area is calculated relative to sensor area (activeRect) - if (captureRequestBuilder != null) { - final Rect computedZoom = cameraZoom.computeZoom(zoom); - captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom); - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); - } - - result.success(null); - } - - private void closeCaptureSession() { - if (cameraCaptureSession != null) { - cameraCaptureSession.close(); - cameraCaptureSession = null; - } - } - - public void close() { - closeCaptureSession(); - - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; - } - if (pictureImageReader != null) { - pictureImageReader.close(); - pictureImageReader = null; - } - if (imageStreamReader != null) { - imageStreamReader.close(); - imageStreamReader = null; - } - if (mediaRecorder != null) { - mediaRecorder.reset(); - mediaRecorder.release(); - mediaRecorder = null; - } - } - - public void dispose() { - close(); - flutterTexture.release(); - orientationEventListener.disable(); - } - - private int getMediaOrientation() { - final int sensorOrientationOffset = - (currentOrientation == ORIENTATION_UNKNOWN) - ? 0 - : (isFrontFacing) ? -currentOrientation : currentOrientation; - return (sensorOrientationOffset + sensorOrientation + 360) % 360; - } + public void close() { + closeCaptureSession(); + + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; + } + if (pictureImageReader != null) { + pictureImageReader.close(); + pictureImageReader = null; + } + if (imageStreamReader != null) { + imageStreamReader.close(); + imageStreamReader = null; + } + if (mediaRecorder != null) { + mediaRecorder.reset(); + mediaRecorder.release(); + mediaRecorder = null; + } + } + + public void dispose() { + close(); + flutterTexture.release(); + orientationEventListener.disable(); + } + + private int getMediaOrientation() { + final int sensorOrientationOffset = + (currentOrientation == ORIENTATION_UNKNOWN) + ? 0 + : (isFrontFacing) ? -currentOrientation : currentOrientation; + return (sensorOrientationOffset + sensorOrientation + 360) % 360; + } } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 2fee13816b51..120e871abb6c 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -5,6 +5,7 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.camera.types.ExposureMode; +import io.flutter.plugins.camera.types.FocusMode; import java.util.HashMap; import java.util.Map; @@ -25,11 +26,15 @@ void sendCameraInitializedEvent( Integer previewWidth, Integer previewHeight, ExposureMode exposureMode, - Boolean exposurePointSupported) { + FocusMode focusMode, + Boolean exposurePointSupported, + Boolean focusPointSupported) { assert (previewWidth != null); assert (previewHeight != null); assert (exposureMode != null); + assert (focusMode != null); assert (exposurePointSupported != null); + assert (focusPointSupported != null); this.send( EventType.INITIALIZED, new HashMap() { @@ -37,7 +42,9 @@ void sendCameraInitializedEvent( put("previewWidth", previewWidth.doubleValue()); put("previewHeight", previewHeight.doubleValue()); put("exposureMode", exposureMode.toString()); + put("focusMode", focusMode.toString()); put("exposurePointSupported", exposurePointSupported); + put("focusPointSupported", focusPointSupported); } }); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 78a10010f90b..8881f6da802f 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -12,6 +12,7 @@ import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; import io.flutter.plugins.camera.types.ExposureMode; import io.flutter.plugins.camera.types.FlashMode; +import io.flutter.plugins.camera.types.FocusMode; import io.flutter.view.TextureRegistry; import java.util.HashMap; import java.util.Map; @@ -206,6 +207,37 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } break; } + case "setFocusMode": + { + String modeStr = call.argument("mode"); + FocusMode mode = FocusMode.getValueForString(modeStr); + if (mode == null) { + result.error("setFocusModeFailed", "Unknown focus mode " + modeStr, null); + return; + } + try { + camera.setFocusMode(result, mode); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "setFocusPoint": + { + Boolean reset = call.argument("reset"); + Double x = null; + Double y = null; + if (reset == null || !reset) { + x = call.argument("x"); + y = call.argument("y"); + } + try { + camera.setFocusPoint(result, x, y); + } catch (Exception e) { + handleException(e, result); + } + break; + } case "startImageStream": { try { diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java new file mode 100644 index 000000000000..30922716da98 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java @@ -0,0 +1,25 @@ +package io.flutter.plugins.camera.types; + +// Mirrors focus_mode.dart +public enum FocusMode { + continuous("continuous"), + auto("auto"); + + private final String strValue; + + FocusMode(String strValue) { + this.strValue = strValue; + } + + public static FocusMode getValueForString(String modeStr) { + for (FocusMode value : values()) { + if (value.strValue.equals(modeStr)) return value; + } + return null; + } + + @Override + public String toString() { + return strValue; + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 52d9829f7d8f..6a71b69b091d 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -7,6 +7,8 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; +import io.flutter.plugins.camera.types.ExposureMode; +import io.flutter.plugins.camera.types.FocusMode; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -58,7 +60,7 @@ public void sendCameraErrorEvent_includesErrorDescriptions() { @Test public void sendCameraInitializedEvent_includesPreviewSize() { - dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, true); + dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, FocusMode.continuous, true, true); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); @@ -67,7 +69,9 @@ public void sendCameraInitializedEvent_includesPreviewSize() { assertEquals(0, (double) call.argument("previewWidth"), 0); assertEquals(0, (double) call.argument("previewHeight"), 0); assertEquals("ExposureMode auto", call.argument("exposureMode"), "auto"); + assertEquals("FocusMode continuous", call.argument("focusMode"), "continuous"); assertEquals("exposurePointSupported", call.argument("exposurePointSupported"), true); + assertEquals("focusPointSupported", call.argument("focusPointSupported"), true); } @Test diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java new file mode 100644 index 000000000000..617dbe4cce63 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java @@ -0,0 +1,30 @@ +package io.flutter.plugins.camera.types; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class FocusModeTest { + + @Test + public void getValueForString_returns_correct_values() { + assertEquals( + "Returns FocusMode.continuous for 'continuous'", FocusMode.getValueForString("continuous"), FocusMode.continuous); + assertEquals( + "Returns FocusMode.auto for 'auto'", + FocusMode.getValueForString("auto"), + FocusMode.auto); + } + + @Test + public void getValueForString_returns_null_for_nonexistant_value() { + assertEquals( + "Returns null for 'nonexistant'", FocusMode.getValueForString("nonexistant"), null); + } + + @Test + public void toString_returns_correct_value() { + assertEquals("Returns 'continuous' for FocusMode.continuous", FocusMode.continuous.toString(), "continuous"); + assertEquals("Returns 'auto' for FocusMode.auto", FocusMode.auto.toString(), "auto"); + } +} diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index c4fa1c5ed01e..c3d37442f882 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -49,6 +49,8 @@ class _CameraExampleHomeState extends State Animation _flashModeControlRowAnimation; AnimationController _exposureModeControlRowAnimationController; Animation _exposureModeControlRowAnimation; + AnimationController _focusModeControlRowAnimationController; + Animation _focusModeControlRowAnimation; double _minAvailableZoom; double _maxAvailableZoom; double _currentScale = 1.0; @@ -77,6 +79,14 @@ class _CameraExampleHomeState extends State parent: _exposureModeControlRowAnimationController, curve: Curves.easeInCubic, ); + _focusModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _focusModeControlRowAnimation = CurvedAnimation( + parent: _focusModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); } @override @@ -249,6 +259,11 @@ class _CameraExampleHomeState extends State onPressed: controller != null ? onExposureModeButtonPressed : null, ), + IconButton( + icon: Icon(Icons.filter_center_focus), + color: Colors.blue, + onPressed: controller != null ? onFocusModeButtonPressed : null, + ), IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, @@ -258,6 +273,7 @@ class _CameraExampleHomeState extends State ), _flashModeControlRowWidget(), _exposureModeControlRowWidget(), + _focusModeControlRowWidget(), ], ); } @@ -383,6 +399,52 @@ class _CameraExampleHomeState extends State ); } + Widget _focusModeControlRowWidget() { + return SizeTransition( + sizeFactor: _focusModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + Center( + child: Text("Focus Mode"), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + children: [ + FlatButton( + child: Text('AUTO'), + textColor: controller?.value?.focusMode == FocusMode.auto + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.auto) + : null, + onLongPress: () { + if (controller != null) controller.setFocusPoint(null); + showInSnackBar('Resetting focus point'); + }, + ), + FlatButton( + child: Text('LOCKED'), + textColor: controller?.value?.focusMode == FocusMode.locked + ? Colors.orange + : Colors.blue, + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.locked) + : null, + ), + ], + ), + ], + ), + ), + ), + ); + } + /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { return Row( @@ -468,10 +530,12 @@ class _CameraExampleHomeState extends State } void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { - controller.setExposurePoint(Offset( + final offset = Offset( details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, - )); + ); + controller.setExposurePoint(offset); + controller.setFocusPoint(offset); } void onNewCameraSelected(CameraDescription cameraDescription) async { @@ -526,6 +590,7 @@ class _CameraExampleHomeState extends State } else { _flashModeControlRowAnimationController.forward(); _exposureModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); } } @@ -535,6 +600,17 @@ class _CameraExampleHomeState extends State } else { _exposureModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); + } + } + + void onFocusModeButtonPressed() { + if (_focusModeControlRowAnimationController.value == 1) { + _focusModeControlRowAnimationController.reverse(); + } else { + _focusModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + _exposureModeControlRowAnimationController.reverse(); } } @@ -559,6 +635,13 @@ class _CameraExampleHomeState extends State }); } + void onSetFocusModeButtonPressed(FocusMode mode) { + setFocusMode(mode).then((_) { + if (mounted) setState(() {}); + showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); + }); + } + void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) setState(() {}); @@ -678,6 +761,15 @@ class _CameraExampleHomeState extends State } } + Future setFocusMode(FocusMode mode) async { + try { + await controller.setFocusMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + Future _startVideoPlayer() async { final VideoPlayerController vController = VideoPlayerController.file(File(videoFile.path)); diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 005ec38fcdfa..0df14d140f95 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -215,6 +215,44 @@ static ExposureMode getExposureModeForString(NSString *mode) { } } +// Mirrors FocusMode in camera.dart +typedef enum { + FocusModeAuto, + FocusModeLocked, +} FocusMode; + +static NSString *getStringForFocusMode(FocusMode mode) { + switch (mode) { + case FocusModeAuto: + return @"auto"; + case FocusModeLocked: + return @"locked"; + } + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown string for focus mode"] + }]; + @throw error; +} + +static FocusMode getFocusModeForString(NSString *mode) { + if ([mode isEqualToString:@"auto"]) { + return FocusModeAuto; + } else if ([mode isEqualToString:@"locked"]) { + return FocusModeLocked; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown focus mode %@", mode] + }]; + @throw error; + } +} + // Mirrors ResolutionPreset in camera.dart typedef enum { veryLow, @@ -283,6 +321,7 @@ @interface FLTCam : NSObject { enableAudio: enableAudio, ); - unawaited(CameraPlatform.instance - .onCameraInitialized(_cameraId) - .first - .then((event) { + unawaited(CameraPlatform.instance.onCameraInitialized(_cameraId).first.then((event) { _initializeCompleter.complete(event); })); @@ -221,15 +232,14 @@ class CameraController extends ValueNotifier { value = value.copyWith( isInitialized: true, - previewSize: await _initializeCompleter.future - .then((CameraInitializedEvent event) => Size( - event.previewWidth, - event.previewHeight, - )), - exposureMode: await _initializeCompleter.future - .then((event) => event.exposureMode), - exposurePointSupported: await _initializeCompleter.future - .then((event) => event.exposurePointSupported), + previewSize: await _initializeCompleter.future.then((CameraInitializedEvent event) => Size( + event.previewWidth, + event.previewHeight, + )), + exposureMode: await _initializeCompleter.future.then((event) => event.exposureMode), + focusMode: await _initializeCompleter.future.then((event) => event.focusMode), + exposurePointSupported: await _initializeCompleter.future.then((event) => event.exposurePointSupported), + focusPointSupported: await _initializeCompleter.future.then((event) => event.focusPointSupported), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); @@ -298,8 +308,7 @@ class CameraController extends ValueNotifier { /// // TODO(bmparr): Add settings for resolution and fps. Future startImageStream(onLatestImageAvailable onAvailable) async { - assert(defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS); + assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS); if (!value.isInitialized || _isDisposed) { throw CameraException( @@ -326,10 +335,8 @@ class CameraController extends ValueNotifier { } on PlatformException catch (e) { throw CameraException(e.code, e.message); } - const EventChannel cameraEventChannel = - EventChannel('plugins.flutter.io/camera/imageStream'); - _imageStreamSubscription = - cameraEventChannel.receiveBroadcastStream().listen( + const EventChannel cameraEventChannel = EventChannel('plugins.flutter.io/camera/imageStream'); + _imageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen( (dynamic imageData) { onAvailable(CameraImage.fromPlatformData(imageData)); }, @@ -344,8 +351,7 @@ class CameraController extends ValueNotifier { /// The `stopImageStream` method is only available on Android and iOS (other /// platforms won't be supported in current setup). Future stopImageStream() async { - assert(defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS); + assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS); if (!value.isInitialized || _isDisposed) { throw CameraException( @@ -571,10 +577,8 @@ class CameraController extends ValueNotifier { /// Sets the exposure point for automatically determining the exposure value. Future setExposurePoint(Offset point) async { - if (point != null && - (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { - throw ArgumentError( - 'The values of point should be anywhere between (0,0) and (1,1).'); + if (point != null && (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { + throw ArgumentError('The values of point should be anywhere between (0,0) and (1,1).'); } try { await CameraPlatform.instance.setExposurePoint( @@ -661,8 +665,7 @@ class CameraController extends ValueNotifier { } // Check if offset is in range - List range = - await Future.wait([getMinExposureOffset(), getMaxExposureOffset()]); + List range = await Future.wait([getMinExposureOffset(), getMaxExposureOffset()]); if (offset < range[0] || offset > range[1]) { throw CameraException( "exposureOffsetOutOfBounds", @@ -690,6 +693,36 @@ class CameraController extends ValueNotifier { } } + /// Sets the focus mode for taking pictures. + Future setFocusMode(FocusMode mode) async { + try { + await CameraPlatform.instance.setFocusMode(_cameraId, mode); + value = value.copyWith(focusMode: mode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the focus point for automatically determining the focus value. + Future setFocusPoint(Offset point) async { + if (point != null && (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { + throw ArgumentError('The values of point should be anywhere between (0,0) and (1,1).'); + } + try { + await CameraPlatform.instance.setFocusPoint( + _cameraId, + point == null + ? null + : Point( + point.dx, + point.dy, + ), + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Releases the resources of this camera. @override Future dispose() async { diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 4b806107fb5b..591e79caf341 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -32,6 +32,8 @@ get mockOnCameraInitializedEvent => CameraInitializedEvent( 75, 75, ExposureMode.auto, + FocusMode.auto, + true, true, ); From bdeed1d213a9f106d0bd80b8905c0ae3af29886e Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 11:52:45 +0100 Subject: [PATCH 09/31] Update platform interface for changes to autofocus methods --- .../lib/src/types/focus_mode.dart | 16 +++---- .../test/events/camera_event_test.dart | 46 +++++++++---------- .../method_channel_camera_test.dart | 16 +++---- .../test/types/focus_mode_test.dart | 8 ++-- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart index 8da2a90fa858..99dcf67d1246 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart @@ -4,18 +4,18 @@ /// The possible focus modes that can be set for a camera. enum FocusMode { - /// Automatically determine focus settings. - auto, + /// Continuously automatically adjust the focus settings. + continuous, - /// Lock the currently determined focus settings. - locked, + /// Automatically determine the focus settings once upon setting this mode or the focus point. + auto, } /// Returns the focus mode as a String. String serializeFocusMode(FocusMode focusMode) { switch (focusMode) { - case FocusMode.locked: - return 'locked'; + case FocusMode.continuous: + return 'continuous'; case FocusMode.auto: return 'auto'; default: @@ -26,8 +26,8 @@ String serializeFocusMode(FocusMode focusMode) { /// Returns the focus mode for a given String. FocusMode deserializeFocusMode(String str) { switch (str) { - case "locked": - return FocusMode.locked; + case "continuous": + return FocusMode.continuous; case "auto": return FocusMode.auto; default: diff --git a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart index e68446e135f0..76e5bd59e4e0 100644 --- a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart +++ b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart @@ -13,13 +13,13 @@ void main() { group('CameraInitializedEvent tests', () { test('Constructor should initialize all properties', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); expect(event.cameraId, 1); expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); - expect(event.focusMode, FocusMode.auto); + expect(event.focusMode, FocusMode.continuous); expect(event.exposurePointSupported, true); expect(event.focusPointSupported, true); }); @@ -30,7 +30,7 @@ void main() { 'previewWidth': 1024.0, 'previewHeight': 640.0, 'exposureMode': 'auto', - 'focusMode': 'auto', + 'focusMode': 'continuous', 'exposurePointSupported': true, 'focusPointSupported': true }); @@ -39,14 +39,14 @@ void main() { expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); - expect(event.focusMode, FocusMode.auto); + expect(event.focusMode, FocusMode.continuous); expect(event.exposurePointSupported, true); expect(event.focusPointSupported, true); }); test('toJson should return a map with all fields', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final jsonMap = event.toJson(); @@ -55,52 +55,52 @@ void main() { expect(jsonMap['previewWidth'], 1024); expect(jsonMap['previewHeight'], 640); expect(jsonMap['exposureMode'], 'auto'); - expect(jsonMap['focusMode'], 'auto'); + expect(jsonMap['focusMode'], 'continuous'); expect(jsonMap['exposurePointSupported'], true); expect(jsonMap['focusPointSupported'], true); }); test('equals should return true if objects are the same', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final secondEvent = CameraInitializedEvent( - 2, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 2, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewWidth is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final secondEvent = CameraInitializedEvent( - 1, 2048, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 2048, 640, ExposureMode.auto, FocusMode.continuous, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewHeight is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 980, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 980, ExposureMode.auto, FocusMode.continuous, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if exposureMode is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.locked, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.locked, FocusMode.continuous, true, true); expect(firstEvent == secondEvent, false); }); @@ -108,34 +108,34 @@ void main() { test('equals should return false if exposurePointSupported is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, false, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, false, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusMode is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.locked, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusPointSupported is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, false); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, false); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); final expectedHashCode = event.cameraId.hashCode ^ event.previewWidth.hashCode ^ event.previewHeight.hashCode ^ diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index c271dc01f6ae..338b9cd606ab 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -125,7 +125,7 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.auto, + FocusMode.continuous, true, true, )); @@ -165,7 +165,7 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.auto, + FocusMode.continuous, true, true, )); @@ -209,7 +209,7 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.auto, + FocusMode.continuous, true, true, )); @@ -228,7 +228,7 @@ void main() { 3840, 2160, ExposureMode.auto, - FocusMode.auto, + FocusMode.continuous, true, true, ); @@ -341,7 +341,7 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.auto, + FocusMode.continuous, true, true, ), @@ -669,15 +669,15 @@ void main() { ); // Act + await camera.setFocusMode(cameraId, FocusMode.continuous); await camera.setFocusMode(cameraId, FocusMode.auto); - await camera.setFocusMode(cameraId, FocusMode.locked); // Assert expect(channel.log, [ isMethodCall('setFocusMode', - arguments: {'cameraId': cameraId, 'mode': 'auto'}), + arguments: {'cameraId': cameraId, 'mode': 'continuous'}), isMethodCall('setFocusMode', - arguments: {'cameraId': cameraId, 'mode': 'locked'}), + arguments: {'cameraId': cameraId, 'mode': 'auto'}), ]); }); diff --git a/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart b/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart index ca7ad902820a..e2df3d1c2e3a 100644 --- a/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart +++ b/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart @@ -15,17 +15,17 @@ void main() { test("FocusMode enum should have items in correct index", () { final values = FocusMode.values; - expect(values[0], FocusMode.auto); - expect(values[1], FocusMode.locked); + expect(values[0], FocusMode.continuous); + expect(values[1], FocusMode.auto); }); test("serializeFocusMode() should serialize correctly", () { + expect(serializeFocusMode(FocusMode.continuous), "continuous"); expect(serializeFocusMode(FocusMode.auto), "auto"); - expect(serializeFocusMode(FocusMode.locked), "locked"); }); test("deserializeFocusMode() should deserialize correctly", () { + expect(deserializeFocusMode('continuous'), FocusMode.continuous); expect(deserializeFocusMode('auto'), FocusMode.auto); - expect(deserializeFocusMode('locked'), FocusMode.locked); }); } From 4e405b0897aa9a97b769b3ff60d861257475e309 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 12:05:20 +0100 Subject: [PATCH 10/31] WIP --- .../src/main/java/io/flutter/plugins/camera/Camera.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 1ac546049aad..703861cd6630 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -729,8 +729,10 @@ public void setFocusMode(@NonNull final Result result, FocusMode mode) throws CameraAccessException { this.focusMode = mode; initPreviewCaptureBuilder(); + captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); cameraCaptureSession.setRepeatingRequest( captureRequestBuilder.build(), pictureCaptureCallback, null); + captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); result.success(null); } @@ -756,8 +758,10 @@ public void setFocusPoint(@NonNull final Result result, Double x, Double y) afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); // Apply it initPreviewCaptureBuilder(); + captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); this.cameraCaptureSession.setRepeatingRequest( captureRequestBuilder.build(), pictureCaptureCallback, null); + captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); result.success(null); } From 798d45894018c3cbc41460324c558164f9725ae8 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 12:07:29 +0100 Subject: [PATCH 11/31] Revert "Update platform interface for changes to autofocus methods" This reverts commit bdeed1d213a9f106d0bd80b8905c0ae3af29886e. --- .../lib/src/types/focus_mode.dart | 16 +++---- .../test/events/camera_event_test.dart | 46 +++++++++---------- .../method_channel_camera_test.dart | 16 +++---- .../test/types/focus_mode_test.dart | 8 ++-- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart index 99dcf67d1246..8da2a90fa858 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart @@ -4,18 +4,18 @@ /// The possible focus modes that can be set for a camera. enum FocusMode { - /// Continuously automatically adjust the focus settings. - continuous, - - /// Automatically determine the focus settings once upon setting this mode or the focus point. + /// Automatically determine focus settings. auto, + + /// Lock the currently determined focus settings. + locked, } /// Returns the focus mode as a String. String serializeFocusMode(FocusMode focusMode) { switch (focusMode) { - case FocusMode.continuous: - return 'continuous'; + case FocusMode.locked: + return 'locked'; case FocusMode.auto: return 'auto'; default: @@ -26,8 +26,8 @@ String serializeFocusMode(FocusMode focusMode) { /// Returns the focus mode for a given String. FocusMode deserializeFocusMode(String str) { switch (str) { - case "continuous": - return FocusMode.continuous; + case "locked": + return FocusMode.locked; case "auto": return FocusMode.auto; default: diff --git a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart index 76e5bd59e4e0..e68446e135f0 100644 --- a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart +++ b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart @@ -13,13 +13,13 @@ void main() { group('CameraInitializedEvent tests', () { test('Constructor should initialize all properties', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(event.cameraId, 1); expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); - expect(event.focusMode, FocusMode.continuous); + expect(event.focusMode, FocusMode.auto); expect(event.exposurePointSupported, true); expect(event.focusPointSupported, true); }); @@ -30,7 +30,7 @@ void main() { 'previewWidth': 1024.0, 'previewHeight': 640.0, 'exposureMode': 'auto', - 'focusMode': 'continuous', + 'focusMode': 'auto', 'exposurePointSupported': true, 'focusPointSupported': true }); @@ -39,14 +39,14 @@ void main() { expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); - expect(event.focusMode, FocusMode.continuous); + expect(event.focusMode, FocusMode.auto); expect(event.exposurePointSupported, true); expect(event.focusPointSupported, true); }); test('toJson should return a map with all fields', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final jsonMap = event.toJson(); @@ -55,52 +55,52 @@ void main() { expect(jsonMap['previewWidth'], 1024); expect(jsonMap['previewHeight'], 640); expect(jsonMap['exposureMode'], 'auto'); - expect(jsonMap['focusMode'], 'continuous'); + expect(jsonMap['focusMode'], 'auto'); expect(jsonMap['exposurePointSupported'], true); expect(jsonMap['focusPointSupported'], true); }); test('equals should return true if objects are the same', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final secondEvent = CameraInitializedEvent( - 2, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 2, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewWidth is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final secondEvent = CameraInitializedEvent( - 1, 2048, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 2048, 640, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewHeight is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 980, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 980, ExposureMode.auto, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if exposureMode is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.locked, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.locked, FocusMode.auto, true, true); expect(firstEvent == secondEvent, false); }); @@ -108,34 +108,34 @@ void main() { test('equals should return false if exposurePointSupported is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, false, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, false, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusMode is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); - final secondEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + final secondEvent = CameraInitializedEvent( + 1, 1024, 640, ExposureMode.auto, FocusMode.locked, true, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusPointSupported is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, false); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, false); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.continuous, true, true); + 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); final expectedHashCode = event.cameraId.hashCode ^ event.previewWidth.hashCode ^ event.previewHeight.hashCode ^ diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 338b9cd606ab..c271dc01f6ae 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -125,7 +125,7 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.continuous, + FocusMode.auto, true, true, )); @@ -165,7 +165,7 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.continuous, + FocusMode.auto, true, true, )); @@ -209,7 +209,7 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.continuous, + FocusMode.auto, true, true, )); @@ -228,7 +228,7 @@ void main() { 3840, 2160, ExposureMode.auto, - FocusMode.continuous, + FocusMode.auto, true, true, ); @@ -341,7 +341,7 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.continuous, + FocusMode.auto, true, true, ), @@ -669,15 +669,15 @@ void main() { ); // Act - await camera.setFocusMode(cameraId, FocusMode.continuous); await camera.setFocusMode(cameraId, FocusMode.auto); + await camera.setFocusMode(cameraId, FocusMode.locked); // Assert expect(channel.log, [ - isMethodCall('setFocusMode', - arguments: {'cameraId': cameraId, 'mode': 'continuous'}), isMethodCall('setFocusMode', arguments: {'cameraId': cameraId, 'mode': 'auto'}), + isMethodCall('setFocusMode', + arguments: {'cameraId': cameraId, 'mode': 'locked'}), ]); }); diff --git a/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart b/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart index e2df3d1c2e3a..ca7ad902820a 100644 --- a/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart +++ b/packages/camera/camera_platform_interface/test/types/focus_mode_test.dart @@ -15,17 +15,17 @@ void main() { test("FocusMode enum should have items in correct index", () { final values = FocusMode.values; - expect(values[0], FocusMode.continuous); - expect(values[1], FocusMode.auto); + expect(values[0], FocusMode.auto); + expect(values[1], FocusMode.locked); }); test("serializeFocusMode() should serialize correctly", () { - expect(serializeFocusMode(FocusMode.continuous), "continuous"); expect(serializeFocusMode(FocusMode.auto), "auto"); + expect(serializeFocusMode(FocusMode.locked), "locked"); }); test("deserializeFocusMode() should deserialize correctly", () { - expect(deserializeFocusMode('continuous'), FocusMode.continuous); expect(deserializeFocusMode('auto'), FocusMode.auto); + expect(deserializeFocusMode('locked'), FocusMode.locked); }); } From 68d3e74721e3b2bdcc821545a5c09864d6e4bc51 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 13:03:33 +0100 Subject: [PATCH 12/31] Finish android implementation --- .../io/flutter/plugins/camera/Camera.java | 50 +++++++++++++------ .../plugins/camera/types/FocusMode.java | 4 +- .../plugins/camera/DartMessengerTest.java | 4 +- .../plugins/camera/types/FocusModeTest.java | 10 ++-- packages/camera/camera/example/lib/main.dart | 2 +- .../camera/camera/test/camera_value_test.dart | 5 +- 6 files changed, 49 insertions(+), 26 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 703861cd6630..adc991139760 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -114,7 +114,7 @@ public Camera( this.applicationContext = activity.getApplicationContext(); this.flashMode = FlashMode.auto; this.exposureMode = ExposureMode.auto; - this.focusMode = FocusMode.continuous; + this.focusMode = FocusMode.auto; this.exposureOffset = 0; orientationEventListener = new OrientationEventListener(activity.getApplicationContext()) { @@ -351,7 +351,7 @@ private void processCapture(CaptureResult result) { private void runPictureAutoFocus() { assert (pictureCaptureRequest != null); pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing); - lockAutoFocus(); + lockAutoFocus(pictureCaptureCallback); } private void runPicturePreCapture() { @@ -412,11 +412,11 @@ public void onCaptureCompleted( } } - private void lockAutoFocus() { + private void lockAutoFocus(CaptureCallback callback) { captureRequestBuilder.set( CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); try { - cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null); + cameraCaptureSession.capture(captureRequestBuilder.build(), callback, null); } catch (CameraAccessException e) { pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); } @@ -729,10 +729,20 @@ public void setFocusMode(@NonNull final Result result, FocusMode mode) throws CameraAccessException { this.focusMode = mode; initPreviewCaptureBuilder(); - captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); - cameraCaptureSession.setRepeatingRequest( - captureRequestBuilder.build(), pictureCaptureCallback, null); - captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); + switch (mode) { + case auto: + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + break; + case locked: + lockAutoFocus(new CaptureCallback() { + @Override + public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + unlockAutoFocus(); + } + }); + break; + } result.success(null); } @@ -756,12 +766,22 @@ public void setFocusPoint(@NonNull final Result result, Double x, Double y) } // Set the metering rectangle afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); - // Apply it + // Apply the new metering rectangle initPreviewCaptureBuilder(); - captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); - this.cameraCaptureSession.setRepeatingRequest( - captureRequestBuilder.build(), pictureCaptureCallback, null); - captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); + switch (focusMode) { + case auto: + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + break; + case locked: + lockAutoFocus(new CaptureCallback() { + @Override + public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + unlockAutoFocus(); + } + }); + break; + } result.success(null); } @@ -927,10 +947,10 @@ private void initPreviewCaptureBuilder() { captureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureOffset); // Applying auto focus switch (focusMode) { - case auto: + case locked: captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); break; - case continuous: + case auto: captureRequestBuilder.set( CaptureRequest.CONTROL_AF_MODE, recordingVideo ? CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO : CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); default: diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java index 30922716da98..10494f26a904 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java @@ -2,8 +2,8 @@ // Mirrors focus_mode.dart public enum FocusMode { - continuous("continuous"), - auto("auto"); + auto("auto"), + locked("locked"); private final String strValue; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 6a71b69b091d..a7a8f930c42d 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -60,7 +60,7 @@ public void sendCameraErrorEvent_includesErrorDescriptions() { @Test public void sendCameraInitializedEvent_includesPreviewSize() { - dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, FocusMode.continuous, true, true); + dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, FocusMode.auto, true, true); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); @@ -69,7 +69,7 @@ public void sendCameraInitializedEvent_includesPreviewSize() { assertEquals(0, (double) call.argument("previewWidth"), 0); assertEquals(0, (double) call.argument("previewHeight"), 0); assertEquals("ExposureMode auto", call.argument("exposureMode"), "auto"); - assertEquals("FocusMode continuous", call.argument("focusMode"), "continuous"); + assertEquals("FocusMode continuous", call.argument("focusMode"), "auto"); assertEquals("exposurePointSupported", call.argument("exposurePointSupported"), true); assertEquals("focusPointSupported", call.argument("focusPointSupported"), true); } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java index 617dbe4cce63..795a9397a735 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java @@ -9,11 +9,11 @@ public class FocusModeTest { @Test public void getValueForString_returns_correct_values() { assertEquals( - "Returns FocusMode.continuous for 'continuous'", FocusMode.getValueForString("continuous"), FocusMode.continuous); + "Returns FocusMode.auto for 'auto'", FocusMode.getValueForString("auto"), FocusMode.auto); assertEquals( - "Returns FocusMode.auto for 'auto'", - FocusMode.getValueForString("auto"), - FocusMode.auto); + "Returns FocusMode.locked for 'locked'", + FocusMode.getValueForString("locked"), + FocusMode.locked); } @Test @@ -24,7 +24,7 @@ public void getValueForString_returns_null_for_nonexistant_value() { @Test public void toString_returns_correct_value() { - assertEquals("Returns 'continuous' for FocusMode.continuous", FocusMode.continuous.toString(), "continuous"); assertEquals("Returns 'auto' for FocusMode.auto", FocusMode.auto.toString(), "auto"); + assertEquals("Returns 'locked' for FocusMode.locked", FocusMode.locked.toString(), "locked"); } } diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index c3d37442f882..141b0fb428ac 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -534,7 +534,7 @@ class _CameraExampleHomeState extends State details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, ); - controller.setExposurePoint(offset); + // controller.setExposurePoint(offset); controller.setFocusPoint(offset); } diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index d9193e212ea9..eb7927b9eb6c 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -106,11 +106,14 @@ void main() { isTakingPicture: false, isStreamingImages: false, flashMode: FlashMode.auto, + exposureMode: ExposureMode.auto, + focusMode: FocusMode.auto, exposurePointSupported: true, + focusPointSupported: true, ); expect(cameraValue.toString(), - 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: null, exposurePointSupported: true)'); + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true)'); }); }); } From d830704c545439cd71e128cd792deab56740c127 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 13:09:19 +0100 Subject: [PATCH 13/31] Fix iOS implementation --- packages/camera/camera/example/lib/main.dart | 2 +- packages/camera/camera/ios/Classes/CameraPlugin.m | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 141b0fb428ac..c3d37442f882 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -534,7 +534,7 @@ class _CameraExampleHomeState extends State details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, ); - // controller.setExposurePoint(offset); + controller.setExposurePoint(offset); controller.setFocusPoint(offset); } diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 0df14d140f95..eb16712cc5a0 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -871,7 +871,7 @@ - (void)applyExposureMode { [_captureDevice lockForConfiguration:nil]; switch (_exposureMode) { case ExposureModeLocked: - [_captureDevice setExposureMode:AVCaptureExposureModeLocked]; + [_captureDevice setExposureMode:AVCaptureExposureModeAutoExpose]; break; case ExposureModeAuto: if ([_captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { @@ -901,7 +901,7 @@ - (void)applyFocusMode { [_captureDevice lockForConfiguration:nil]; switch (_focusMode) { case FocusModeLocked: - [_captureDevice setFocusMode:AVCaptureFocusModeLocked]; + [_captureDevice setFocusMode:AVCaptureFocusModeAutoFocus]; break; case FocusModeAuto: if ([_captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { @@ -922,7 +922,7 @@ - (void)setExposurePointWithResult:(FlutterResult)result x:(double)x y:(double)y return; } [_captureDevice lockForConfiguration:nil]; - [_captureDevice setExposurePointOfInterest:CGPointMake(x, y)]; + [_captureDevice setExposurePointOfInterest:CGPointMake(y, 1-x)]; [_captureDevice unlockForConfiguration]; // Retrigger auto exposure [self applyExposureMode]; @@ -937,7 +937,7 @@ - (void)setFocusPointWithResult:(FlutterResult)result x:(double)x y:(double)y { return; } [_captureDevice lockForConfiguration:nil]; - [_captureDevice setFocusPointOfInterest:CGPointMake(x, y)]; + [_captureDevice setFocusPointOfInterest:CGPointMake(y, 1-x)]; [_captureDevice unlockForConfiguration]; // Retrigger auto focus [self applyFocusMode]; From 2b98bdd24c5f61f3d8b547122f6ed315646ab17b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 13:16:03 +0100 Subject: [PATCH 14/31] iOS fix for setting the exposure point --- .../camera/camera/ios/Classes/CameraPlugin.m | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 005ec38fcdfa..87661f4a5de0 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -824,15 +824,19 @@ - (void)setExposureModeWithResult:(FlutterResult)result mode:(NSString *)modeStr return; } _exposureMode = mode; - [self applyExposureMode]; + [self applyExposureMode:mode]; result(nil); } -- (void)applyExposureMode { +- (void)applyExposureMode:(ExposureMode)mode { [_captureDevice lockForConfiguration:nil]; - switch (_exposureMode) { + switch (mode) { case ExposureModeLocked: - [_captureDevice setExposureMode:AVCaptureExposureModeLocked]; + if (mode == _exposureMode) { + [_captureDevice setExposureMode:AVCaptureExposureModeAutoExpose]; + } else { + [_captureDevice setExposureMode:AVCaptureExposureModeLocked]; + } break; case ExposureModeAuto: if ([_captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { @@ -853,10 +857,10 @@ - (void)setExposurePointWithResult:(FlutterResult)result x:(double)x y:(double)y return; } [_captureDevice lockForConfiguration:nil]; - [_captureDevice setExposurePointOfInterest:CGPointMake(x, y)]; + [_captureDevice setExposurePointOfInterest:CGPointMake(y, 1 - x)]; [_captureDevice unlockForConfiguration]; // Retrigger auto exposure - [self applyExposureMode]; + [self applyExposureMode:_exposureMode]; result(nil); } From 06f436f08fbf52dffe64eb9d07ad2d888fdc7d71 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 13:21:48 +0100 Subject: [PATCH 15/31] Removed unnecessary check --- packages/camera/camera/ios/Classes/CameraPlugin.m | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 87661f4a5de0..816792e2fc1d 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -824,19 +824,15 @@ - (void)setExposureModeWithResult:(FlutterResult)result mode:(NSString *)modeStr return; } _exposureMode = mode; - [self applyExposureMode:mode]; + [self applyExposureMode]; result(nil); } -- (void)applyExposureMode:(ExposureMode)mode { +- (void)applyExposureMode { [_captureDevice lockForConfiguration:nil]; - switch (mode) { + switch (_exposureMode) { case ExposureModeLocked: - if (mode == _exposureMode) { - [_captureDevice setExposureMode:AVCaptureExposureModeAutoExpose]; - } else { - [_captureDevice setExposureMode:AVCaptureExposureModeLocked]; - } + [_captureDevice setExposureMode:AVCaptureExposureModeAutoExpose]; break; case ExposureModeAuto: if ([_captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { @@ -860,7 +856,7 @@ - (void)setExposurePointWithResult:(FlutterResult)result x:(double)x y:(double)y [_captureDevice setExposurePointOfInterest:CGPointMake(y, 1 - x)]; [_captureDevice unlockForConfiguration]; // Retrigger auto exposure - [self applyExposureMode:_exposureMode]; + [self applyExposureMode]; result(nil); } From df1c09b5c3c2f23627946df6bd6a08bf01012982 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 13:29:37 +0100 Subject: [PATCH 16/31] Updated changelog and pubspec.yaml --- packages/camera/camera_platform_interface/CHANGELOG.md | 4 ++++ packages/camera/camera_platform_interface/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 916d79c70ec1..c71165c4374b 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.6 + +- Added interface methods to support auto focus. + ## 1.0.5 - Added interface to support automatic exposure. diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 998fd616d7aa..893f0444582d 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the camera plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.0.5 +version: 1.0.6 dependencies: flutter: From ff45da44f31ffebd6d81f935dba3bc43f2a48285 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 25 Dec 2020 13:31:33 +0100 Subject: [PATCH 17/31] Updated changelog and pubspec.yaml --- packages/camera/camera/CHANGELOG.md | 4 ++++ packages/camera/camera/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index a80b8286362a..76d0b75dccab 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.5 + +* Adds auto focus support for Android and iOS implementations. + ## 0.6.4 * Adds auto exposure support for Android and iOS implementations. diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 144ecc8df60d..e3727b897a6e 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.6.4 +version: 0.6.5 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: From 14c6ab654d8dce6050026e4e1404f497a0514931 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 29 Dec 2020 11:22:03 +0100 Subject: [PATCH 18/31] Update platform interface dependency --- packages/camera/camera/pubspec.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 144ecc8df60d..da67be5404e1 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -8,9 +8,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - #TODO(BeMacized): Replace with reference to pub.dev version once updated platform interface has been published. - camera_platform_interface: - path: ../camera_platform_interface + camera_platform_interface: ^1.2.0 dev_dependencies: path_provider: ^0.5.0 video_player: ^0.10.0 From a52f690c42a937c909810d86e6835872bd1b4be3 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 29 Dec 2020 17:13:52 +0100 Subject: [PATCH 19/31] Implement PR feedback --- .../io/flutter/plugins/camera/Camera.java | 67 +++++-------- .../flutter/plugins/camera/CameraRegions.java | 55 +++++++++++ .../plugins/camera/CameraRegionsTest.java | 99 +++++++++++++++++++ .../plugins/camera/DartMessengerTest.java | 1 + packages/camera/camera/pubspec.yaml | 2 +- 5 files changed, 181 insertions(+), 43 deletions(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 689df4e3afa6..90fd21340162 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -86,7 +86,7 @@ public class Camera { private FlashMode flashMode; private ExposureMode exposureMode; private PictureCaptureRequest pictureCaptureRequest; - private MeteringRectangle aeMeteringRectangle; + private CameraRegions cameraRegions; private int exposureOffset; public Camera( @@ -167,6 +167,7 @@ public void open() throws CameraAccessException { public void onOpened(@NonNull CameraDevice device) { cameraDevice = device; try { + cameraRegions = new CameraRegions(getRegionBoundaries()); startPreview(); dartMessenger.sendCameraInitializedEvent( previewSize.getWidth(), @@ -702,30 +703,14 @@ public void setExposurePoint(@NonNull final Result result, Double x, Double y) x = 0.5; y = 0.5; } - // Get the current exposure point boundaries. - Size maxBoundaries = getExposureBoundaries(); + // Get the current region boundaries. + Size maxBoundaries = getRegionBoundaries(); if (maxBoundaries == null) { result.error("setExposurePointFailed", "Could not determine max region boundaries", null); return; } - // Interpolate the target coordinate - int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1))); - int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1))); - // Determine the dimensions of the metering triangle (1th of the viewport) - int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d); - int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d); - // Adjust target coordinate to represent top-left corner of metering rectangle - targetX -= targetWidth / 2; - targetY -= targetHeight / 2; - // Adjust target coordinate as to not fall out of bounds - if (targetX < 0) targetX = 0; - if (targetY < 0) targetY = 0; - int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth; - int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight; - if (targetX > maxTargetX) targetX = maxTargetX; - if (targetY > maxTargetY) targetY = maxTargetY; // Set the metering rectangle - aeMeteringRectangle = new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1); + cameraRegions.setAutoExposureMeteringRectangleFromPoint(x, y); // Apply it initPreviewCaptureBuilder(); this.cameraCaptureSession.setRepeatingRequest( @@ -733,25 +718,23 @@ public void setExposurePoint(@NonNull final Result result, Double x, Double y) result.success(null); } - @SuppressLint("NewApi") - private Size getExposureBoundaries() throws CameraAccessException { - // Check if the device supports distortion correction - boolean supportsDistortionCorrection = false; - if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.P) { - int[] availableDistortionCorrectionModes = - cameraManager - .getCameraCharacteristics(cameraDevice.getId()) - .get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); - if (availableDistortionCorrectionModes == null) - availableDistortionCorrectionModes = new int[0]; - long nonOffModesSupported = - Arrays.stream(availableDistortionCorrectionModes) - .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) - .count(); - supportsDistortionCorrection = nonOffModesSupported > 0; - } + @TargetApi(VERSION_CODES.P) + private boolean supportsDistortionCorrection() throws CameraAccessException { + int[] availableDistortionCorrectionModes = + cameraManager + .getCameraCharacteristics(cameraDevice.getId()) + .get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); + if (availableDistortionCorrectionModes == null) availableDistortionCorrectionModes = new int[0]; + long nonOffModesSupported = + Arrays.stream(availableDistortionCorrectionModes) + .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) + .count(); + return nonOffModesSupported > 0; + } + + private Size getRegionBoundaries() throws CameraAccessException { // No distortion correction support - if (!supportsDistortionCorrection) { + if (android.os.Build.VERSION.SDK_INT < VERSION_CODES.P || !supportsDistortionCorrection()) { return cameraManager .getCameraCharacteristics(cameraDevice.getId()) .get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); @@ -850,10 +833,10 @@ private void initPreviewCaptureBuilder() { break; } // Applying auto exposure - if (aeMeteringRectangle != null) { - captureRequestBuilder.set( - CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {aeMeteringRectangle}); - } + MeteringRectangle aeRect = cameraRegions.getAEMeteringRectangle(); + captureRequestBuilder.set( + CaptureRequest.CONTROL_AE_REGIONS, + new MeteringRectangle[] {cameraRegions.getAEMeteringRectangle()}); switch (exposureMode) { case locked: captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java new file mode 100644 index 000000000000..5c58b0741af7 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java @@ -0,0 +1,55 @@ +package io.flutter.plugins.camera; + +import android.hardware.camera2.params.MeteringRectangle; +import android.util.Size; + +public final class CameraRegions { + private MeteringRectangle aeMeteringRectangle; + private Size maxBoundaries; + + public CameraRegions(Size maxBoundaries) { + assert (maxBoundaries != null); + assert (maxBoundaries.getWidth() > 0); + assert (maxBoundaries.getHeight() > 0); + this.maxBoundaries = maxBoundaries; + setAutoExposureMeteringRectangleFromPoint(0.5, 0.5); + } + + public MeteringRectangle getAEMeteringRectangle() { + return aeMeteringRectangle; + } + + public Size getMaxBoundaries() { + return this.maxBoundaries; + } + + public void setAutoExposureMeteringRectangleFromPoint(double x, double y) { + this.aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y); + } + + public MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) { + assert (x >= 0 && x <= 1); + assert (y >= 0 && y <= 1); + assert (maxBoundaries != null); + + // Interpolate the target coordinate + int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1))); + int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1))); + // Determine the dimensions of the metering triangle (10th of the viewport) + int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d); + int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d); + // Adjust target coordinate to represent top-left corner of metering rectangle + targetX -= targetWidth / 2; + targetY -= targetHeight / 2; + // Adjust target coordinate as to not fall out of bounds + if (targetX < 0) targetX = 0; + if (targetY < 0) targetY = 0; + int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth; + int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight; + if (targetX > maxTargetX) targetX = maxTargetX; + if (targetY > maxTargetY) targetY = maxTargetY; + + // Build the metering rectangle + return new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java new file mode 100644 index 000000000000..56f8a20a5644 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java @@ -0,0 +1,99 @@ +package io.flutter.plugins.camera; + +import static org.junit.Assert.assertEquals; + +import android.hardware.camera2.params.MeteringRectangle; +import android.util.Size; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class CameraRegionsTest { + + CameraRegions cameraRegions; + + @Before + public void setUp() { + this.cameraRegions = new CameraRegions(new Size(100, 50)); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_x_upper_bound() { + cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 1.5, 0); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_x_lower_bound() { + cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), -0.5, 0); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_y_upper_bound() { + cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 0, 1.5); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_y_lower_bound() { + cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 0, -0.5); + } + + @Test(expected = AssertionError.class) + public void getMeteringRectangleForPoint_should_throw_for_null_boundaries() { + cameraRegions.getMeteringRectangleForPoint(null, 0, -0); + } + + @Test + public void getMeteringRectangleForPoint_should_return_valid_MeteringRectangle() { + MeteringRectangle r; + // Center + r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.5, 0.5); + assertEquals(new MeteringRectangle(45, 23, 10, 5, 1), r); + + // Top left + r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.0, 0.0); + assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), r); + + // Bottom right + r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 1.0, 1.0); + assertEquals(new MeteringRectangle(89, 44, 10, 5, 1), r); + + // Top left + r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.0, 1.0); + assertEquals(new MeteringRectangle(0, 44, 10, 5, 1), r); + + // Top right + r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 1.0, 0.0); + assertEquals(new MeteringRectangle(89, 0, 10, 5, 1), r); + } + + @Test(expected = AssertionError.class) + public void constructor_should_throw_for_null_boundaries() { + new CameraRegions(null); + } + + @Test(expected = AssertionError.class) + public void constructor_should_throw_for_0_width_boundary() { + new CameraRegions(new Size(0, 50)); + } + + @Test(expected = AssertionError.class) + public void constructor_should_throw_for_0_height_boundary() { + new CameraRegions(new Size(100, 0)); + } + + @Test + public void constructor_should_initialize() { + CameraRegions cr = new CameraRegions(new Size(100, 50)); + assertEquals(new Size(100, 50), cr.getMaxBoundaries()); + assertEquals(new MeteringRectangle(45, 23, 10, 5, 1), cr.getAEMeteringRectangle()); + } + + @Test + public void setAutoExposureMeteringRectangleFromPoin_should_set_aeMeteringRectangle_for_point() { + CameraRegions cr = new CameraRegions(new Size(100, 50)); + cr.setAutoExposureMeteringRectangleFromPoint(0, 0); + assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAEMeteringRectangle()); + } +} diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index 52d9829f7d8f..f91bf82c7063 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -7,6 +7,7 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; +import io.flutter.plugins.camera.types.ExposureMode; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index da67be5404e1..054440025d47 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -9,6 +9,7 @@ dependencies: flutter: sdk: flutter camera_platform_interface: ^1.2.0 + pedantic: ^1.8.0 dev_dependencies: path_provider: ^0.5.0 video_player: ^0.10.0 @@ -16,7 +17,6 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - pedantic: ^1.8.0 mockito: ^4.1.3 plugin_platform_interface: ^1.0.3 From 9a88bca6910b0e5255ec106c2b9a473fca44da18 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 29 Dec 2020 18:10:40 +0100 Subject: [PATCH 20/31] Restore test --- packages/camera/camera/test/camera_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 50ad899f7d98..5a4a7fc8771b 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -27,7 +27,8 @@ get mockAvailableCameras => [ get mockInitializeCamera => 13; -get mockOnCameraInitializedEvent => CameraInitializedEvent(13, 75, 75); +get mockOnCameraInitializedEvent => + CameraInitializedEvent(13, 75, 75, ExposureMode.auto, true); get mockOnCameraClosingEvent => null; @@ -1034,7 +1035,8 @@ class MockCameraPlatform extends Mock : Future.value(mockTakePicture); @override - Future startVideoRecording(int cameraId) => + Future startVideoRecording(int cameraId, + {Duration maxVideoDuration}) => Future.value(mockVideoRecordingXFile); } From 171fe0b25eb472c19af0266b726ff1fd96fb258d Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 30 Dec 2020 09:19:36 +0100 Subject: [PATCH 21/31] Revert test change --- packages/camera/camera/test/camera_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 5a4a7fc8771b..0a799dda7e24 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -28,7 +28,7 @@ get mockAvailableCameras => [ get mockInitializeCamera => 13; get mockOnCameraInitializedEvent => - CameraInitializedEvent(13, 75, 75, ExposureMode.auto, true); + CameraInitializedEvent(13, 75, 75, ExposureMode.auto, false); get mockOnCameraClosingEvent => null; From 930d901e5c938088d0ad2f3874c976353e877bc7 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 30 Dec 2020 09:50:28 +0100 Subject: [PATCH 22/31] Update camera pubspec --- packages/camera/camera/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 0811ac442852..97f257536701 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -8,7 +8,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - camera_platform_interface: ^1.2.0 + camera_platform_interface: '>=1.0.4 <1.3.0' pedantic: ^1.8.0 dev_dependencies: From 60a1137ce9e7aec73b04426ad1cfed960965ef47 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 30 Dec 2020 10:46:37 +0100 Subject: [PATCH 23/31] Update platform interface to prevent breaking changes with current master --- .../lib/src/events/camera_event.dart | 18 ++++---- .../method_channel/method_channel_camera.dart | 2 +- .../lib/src/types/exposure_mode.dart | 2 + .../lib/src/types/focus_mode.dart | 2 + .../test/events/camera_event_test.dart | 46 +++++++++---------- .../method_channel_camera_test.dart | 10 ++-- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart index e9ca14a92d92..8ca445a2e06e 100644 --- a/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart +++ b/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart @@ -69,12 +69,12 @@ class CameraInitializedEvent extends CameraEvent { CameraInitializedEvent( int cameraId, this.previewWidth, - this.previewHeight, + this.previewHeight, [ this.exposureMode, + this.exposurePointSupported = false, this.focusMode, - this.exposurePointSupported, - this.focusPointSupported, - ) : super(cameraId); + this.focusPointSupported = false, + ]) : super(cameraId); /// Converts the supplied [Map] to an instance of the [CameraInitializedEvent] /// class. @@ -82,9 +82,9 @@ class CameraInitializedEvent extends CameraEvent { : previewWidth = json['previewWidth'], previewHeight = json['previewHeight'], exposureMode = deserializeExposureMode(json['exposureMode']), + exposurePointSupported = json['exposurePointSupported'] ?? false, focusMode = deserializeFocusMode(json['focusMode']), - exposurePointSupported = json['exposurePointSupported'], - focusPointSupported = json['focusPointSupported'], + focusPointSupported = json['focusPointSupported'] ?? false, super(json['cameraId']); /// Converts the [CameraInitializedEvent] instance into a [Map] instance that @@ -94,8 +94,8 @@ class CameraInitializedEvent extends CameraEvent { 'previewWidth': previewWidth, 'previewHeight': previewHeight, 'exposureMode': serializeExposureMode(exposureMode), - 'focusMode': serializeFocusMode(focusMode), 'exposurePointSupported': exposurePointSupported, + 'focusMode': serializeFocusMode(focusMode), 'focusPointSupported': focusPointSupported, }; @@ -108,8 +108,8 @@ class CameraInitializedEvent extends CameraEvent { previewWidth == other.previewWidth && previewHeight == other.previewHeight && exposureMode == other.exposureMode && - focusMode == other.focusMode && exposurePointSupported == other.exposurePointSupported && + focusMode == other.focusMode && focusPointSupported == other.focusPointSupported; @override @@ -118,8 +118,8 @@ class CameraInitializedEvent extends CameraEvent { previewWidth.hashCode ^ previewHeight.hashCode ^ exposureMode.hashCode ^ - focusMode.hashCode ^ exposurePointSupported.hashCode ^ + focusMode.hashCode ^ focusPointSupported.hashCode; } diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 3e7b806ab2c8..ba932fb9f34b 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -353,8 +353,8 @@ class MethodChannelCamera extends CameraPlatform { call.arguments['previewWidth'], call.arguments['previewHeight'], deserializeExposureMode(call.arguments['exposureMode']), - deserializeFocusMode(call.arguments['focusMode']), call.arguments['exposurePointSupported'], + deserializeFocusMode(call.arguments['focusMode']), call.arguments['focusPointSupported'], )); break; diff --git a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart index 836f53826479..7fbfbaf5f4a9 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart @@ -13,6 +13,7 @@ enum ExposureMode { /// Returns the exposure mode as a String. String serializeExposureMode(ExposureMode exposureMode) { + if (exposureMode == null) return null; switch (exposureMode) { case ExposureMode.locked: return 'locked'; @@ -25,6 +26,7 @@ String serializeExposureMode(ExposureMode exposureMode) { /// Returns the exposure mode for a given String. ExposureMode deserializeExposureMode(String str) { + if (str == null) return null; switch (str) { case "locked": return ExposureMode.locked; diff --git a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart index 8da2a90fa858..ad5e9a2d46f1 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart @@ -13,6 +13,7 @@ enum FocusMode { /// Returns the focus mode as a String. String serializeFocusMode(FocusMode focusMode) { + if (focusMode == null) return null; switch (focusMode) { case FocusMode.locked: return 'locked'; @@ -25,6 +26,7 @@ String serializeFocusMode(FocusMode focusMode) { /// Returns the focus mode for a given String. FocusMode deserializeFocusMode(String str) { + if (str == null) return null; switch (str) { case "locked": return FocusMode.locked; diff --git a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart index e68446e135f0..f146b7e7e7bc 100644 --- a/packages/camera/camera_platform_interface/test/events/camera_event_test.dart +++ b/packages/camera/camera_platform_interface/test/events/camera_event_test.dart @@ -13,7 +13,7 @@ void main() { group('CameraInitializedEvent tests', () { test('Constructor should initialize all properties', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); expect(event.cameraId, 1); expect(event.previewWidth, 1024); @@ -30,8 +30,8 @@ void main() { 'previewWidth': 1024.0, 'previewHeight': 640.0, 'exposureMode': 'auto', - 'focusMode': 'auto', 'exposurePointSupported': true, + 'focusMode': 'auto', 'focusPointSupported': true }); @@ -39,14 +39,14 @@ void main() { expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); - expect(event.focusMode, FocusMode.auto); expect(event.exposurePointSupported, true); + expect(event.focusMode, FocusMode.auto); expect(event.focusPointSupported, true); }); test('toJson should return a map with all fields', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final jsonMap = event.toJson(); @@ -55,52 +55,52 @@ void main() { expect(jsonMap['previewWidth'], 1024); expect(jsonMap['previewHeight'], 640); expect(jsonMap['exposureMode'], 'auto'); - expect(jsonMap['focusMode'], 'auto'); expect(jsonMap['exposurePointSupported'], true); + expect(jsonMap['focusMode'], 'auto'); expect(jsonMap['focusPointSupported'], true); }); test('equals should return true if objects are the same', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final secondEvent = CameraInitializedEvent( - 2, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 2, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewWidth is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final secondEvent = CameraInitializedEvent( - 1, 2048, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 2048, 640, ExposureMode.auto, true, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewHeight is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 980, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 980, ExposureMode.auto, true, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if exposureMode is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.locked, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.locked, true, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); @@ -108,40 +108,40 @@ void main() { test('equals should return false if exposurePointSupported is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, false, true); + 1, 1024, 640, ExposureMode.auto, false, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusMode is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.locked, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.locked, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusPointSupported is different', () { final firstEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final secondEvent = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, false); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, false); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { final event = CameraInitializedEvent( - 1, 1024, 640, ExposureMode.auto, FocusMode.auto, true, true); + 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final expectedHashCode = event.cameraId.hashCode ^ event.previewWidth.hashCode ^ event.previewHeight.hashCode ^ event.exposureMode.hashCode ^ - event.focusMode.hashCode ^ event.exposurePointSupported.hashCode ^ + event.focusMode.hashCode ^ event.focusPointSupported.hashCode; expect(event.hashCode, expectedHashCode); diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 2fab5e8529b3..35e211531d28 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -125,8 +125,8 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.auto, true, + FocusMode.auto, true, )); await initializeFuture; @@ -165,8 +165,8 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.auto, true, + FocusMode.auto, true, )); await initializeFuture; @@ -209,8 +209,8 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.auto, true, + FocusMode.auto, true, )); await initializeFuture; @@ -228,8 +228,8 @@ void main() { 3840, 2160, ExposureMode.auto, - FocusMode.auto, true, + FocusMode.auto, true, ); await camera.handleMethodCall( @@ -341,8 +341,8 @@ void main() { 1920, 1080, ExposureMode.auto, - FocusMode.auto, true, + FocusMode.auto, true, ), ); From b0149e941433b753db09376616a5288ae2c68bc1 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 30 Dec 2020 10:49:45 +0100 Subject: [PATCH 24/31] Update test to match platform interface updates --- packages/camera/camera/test/camera_test.dart | 1002 +++++++++--------- 1 file changed, 502 insertions(+), 500 deletions(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 2c20117b5365..1725bb14e362 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -14,7 +14,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -get mockAvailableCameras => [ +get mockAvailableCameras => + [ CameraDescription( name: 'camBack', lensDirection: CameraLensDirection.back, @@ -27,13 +28,14 @@ get mockAvailableCameras => [ get mockInitializeCamera => 13; -get mockOnCameraInitializedEvent => CameraInitializedEvent( +get mockOnCameraInitializedEvent => + CameraInitializedEvent( 13, 75, 75, ExposureMode.auto, - FocusMode.auto, true, + FocusMode.auto, true, ); @@ -52,31 +54,31 @@ void main() { group('camera', () { test('debugCheckIsDisposed should not throw assertion error when disposed', - () { - final MockCameraDescription description = MockCameraDescription(); - final CameraController controller = CameraController( - description, - ResolutionPreset.low, - ); + () { + final MockCameraDescription description = MockCameraDescription(); + final CameraController controller = CameraController( + description, + ResolutionPreset.low, + ); - controller.dispose(); + controller.dispose(); - expect(controller.debugCheckIsDisposed, returnsNormally); - }); + expect(controller.debugCheckIsDisposed, returnsNormally); + }); test('debugCheckIsDisposed should throw assertion error when not disposed', - () { - final MockCameraDescription description = MockCameraDescription(); - final CameraController controller = CameraController( - description, - ResolutionPreset.low, - ); - - expect( - () => controller.debugCheckIsDisposed(), - throwsAssertionError, - ); - }); + () { + final MockCameraDescription description = MockCameraDescription(); + final CameraController controller = CameraController( + description, + ResolutionPreset.low, + ); + + expect( + () => controller.debugCheckIsDisposed(), + throwsAssertionError, + ); + }); test('availableCameras() has camera', () async { CameraPlatform.instance = MockCameraPlatform(); @@ -144,32 +146,32 @@ void main() { expect( cameraController.initialize, throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Error description', 'initialize was called on a disposed CameraController', ))); }); test('initialize() throws $CameraException on $PlatformException ', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - mockPlatformException = true; - - expect( - cameraController.initialize, - throwsA(isA().having( - (error) => error.description, - 'foo', - 'bar', - ))); - mockPlatformException = false; - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + mockPlatformException = true; + + expect( + cameraController.initialize, + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); test('prepareForVideoRecording() calls $CameraPlatform ', () async { CameraController cameraController = CameraController( @@ -195,32 +197,32 @@ void main() { expect( cameraController.takePicture(), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController.', 'takePicture was called on uninitialized CameraController', ))); }); test('takePicture() throws $CameraException when takePicture is true', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isTakingPicture: true); - expect( - cameraController.takePicture(), - throwsA(isA().having( - (error) => error.description, - 'Previous capture has not returned yet.', - 'takePicture was called before the previous capture returned.', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isTakingPicture: true); + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'Previous capture has not returned yet.', + 'takePicture was called before the previous capture returned.', + ))); + }); test('takePicture() returns $XFile', () async { CameraController cameraController = CameraController( @@ -236,107 +238,107 @@ void main() { }); test('takePicture() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - mockPlatformException = true; - expect( - cameraController.takePicture(), - throwsA(isA().having( - (error) => error.description, - 'foo', - 'bar', - ))); - mockPlatformException = false; - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + mockPlatformException = true; + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); test('startVideoRecording() throws $CameraException when uninitialized', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController', - 'startVideoRecording was called on uninitialized CameraController', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ))); + }); test('startVideoRecording() throws $CameraException when recording videos', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'A video recording is already started.', - 'startVideoRecording was called when a recording is already started.', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ))); + }); test( 'startVideoRecording() throws $CameraException when already streaming images', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isStreamingImages: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ))); + }); test('getMaxZoomLevel() throws $CameraException when uninitialized', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.getMaxZoomLevel, - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController', - 'getMaxZoomLevel was called on uninitialized CameraController', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.getMaxZoomLevel, + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'getMaxZoomLevel was called on uninitialized CameraController', + ))); + }); test('getMaxZoomLevel() throws $CameraException when disposed', () async { CameraController cameraController = CameraController( @@ -352,7 +354,7 @@ void main() { expect( cameraController.getMaxZoomLevel, throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController', 'getMaxZoomLevel was called on uninitialized CameraController', ))); @@ -360,31 +362,31 @@ void main() { test( 'getMaxZoomLevel() throws $CameraException when a platform exception occured.', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - when(CameraPlatform.instance.getMaxZoomLevel(mockInitializeCamera)) - .thenThrow(PlatformException( + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance.getMaxZoomLevel(mockInitializeCamera)) + .thenThrow(PlatformException( code: 'TEST_ERROR', message: 'This is a test error messge', details: null)); - expect( - cameraController.getMaxZoomLevel, - throwsA(isA() - .having((error) => error.code, 'code', 'TEST_ERROR') - .having( - (error) => error.description, + expect( + cameraController.getMaxZoomLevel, + throwsA(isA() + .having((error) => error.code, 'code', 'TEST_ERROR') + .having( + (error) => error.description, 'description', 'This is a test error messge', ))); - }); + }); test('getMaxZoomLevel() returns max zoom level.', () async { CameraController cameraController = CameraController( @@ -403,22 +405,22 @@ void main() { }); test('getMinZoomLevel() throws $CameraException when uninitialized', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.getMinZoomLevel, - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController', - 'getMinZoomLevel was called on uninitialized CameraController', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.getMinZoomLevel, + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'getMinZoomLevel was called on uninitialized CameraController', + ))); + }); test('getMinZoomLevel() throws $CameraException when disposed', () async { CameraController cameraController = CameraController( @@ -434,7 +436,7 @@ void main() { expect( cameraController.getMinZoomLevel, throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController', 'getMinZoomLevel was called on uninitialized CameraController', ))); @@ -442,31 +444,31 @@ void main() { test( 'getMinZoomLevel() throws $CameraException when a platform exception occured.', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - when(CameraPlatform.instance.getMinZoomLevel(mockInitializeCamera)) - .thenThrow(PlatformException( + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance.getMinZoomLevel(mockInitializeCamera)) + .thenThrow(PlatformException( code: 'TEST_ERROR', message: 'This is a test error messge', details: null)); - expect( - cameraController.getMinZoomLevel, - throwsA(isA() - .having((error) => error.code, 'code', 'TEST_ERROR') - .having( - (error) => error.description, + expect( + cameraController.getMinZoomLevel, + throwsA(isA() + .having((error) => error.code, 'code', 'TEST_ERROR') + .having( + (error) => error.description, 'description', 'This is a test error messge', ))); - }); + }); test('getMinZoomLevel() returns max zoom level.', () async { CameraController cameraController = CameraController( @@ -493,9 +495,9 @@ void main() { ResolutionPreset.max); expect( - () => cameraController.setZoomLevel(42.0), + () => cameraController.setZoomLevel(42.0), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController', 'setZoomLevel was called on uninitialized CameraController', ))); @@ -513,9 +515,9 @@ void main() { await cameraController.dispose(); expect( - () => cameraController.setZoomLevel(42.0), + () => cameraController.setZoomLevel(42.0), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController', 'setZoomLevel was called on uninitialized CameraController', ))); @@ -523,50 +525,50 @@ void main() { test( 'setZoomLevel() throws $CameraException when a platform exception occured.', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - when(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) - .thenThrow(PlatformException( + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) + .thenThrow(PlatformException( code: 'TEST_ERROR', message: 'This is a test error messge', details: null)); - expect( - () => cameraController.setZoomLevel(42), - throwsA(isA() - .having((error) => error.code, 'code', 'TEST_ERROR') - .having( - (error) => error.description, + expect( + () => cameraController.setZoomLevel(42), + throwsA(isA() + .having((error) => error.code, 'code', 'TEST_ERROR') + .having( + (error) => error.description, 'description', 'This is a test error messge', ))); - reset(CameraPlatform.instance); - }); + reset(CameraPlatform.instance); + }); test( 'setZoomLevel() completes and calls method channel with correct value.', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); - await cameraController.initialize(); - await cameraController.setZoomLevel(42.0); + await cameraController.initialize(); + await cameraController.setZoomLevel(42.0); - verify(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) - .called(1); - }); + verify(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) + .called(1); + }); test('setFlashMode() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -580,38 +582,38 @@ void main() { await cameraController.setFlashMode(FlashMode.always); verify(CameraPlatform.instance - .setFlashMode(cameraController.cameraId, FlashMode.always)) + .setFlashMode(cameraController.cameraId, FlashMode.always)) .called(1); }); test('setFlashMode() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .setFlashMode(cameraController.cameraId, FlashMode.always)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.setFlashMode(FlashMode.always), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setFlashMode(FlashMode.always), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('setExposureMode() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -625,38 +627,38 @@ void main() { await cameraController.setExposureMode(ExposureMode.auto); verify(CameraPlatform.instance - .setExposureMode(cameraController.cameraId, ExposureMode.auto)) + .setExposureMode(cameraController.cameraId, ExposureMode.auto)) .called(1); }); test('setExposureMode() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .setExposureMode(cameraController.cameraId, ExposureMode.auto)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.setExposureMode(ExposureMode.auto), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposureMode(ExposureMode.auto), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('setExposurePoint() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -670,38 +672,38 @@ void main() { await cameraController.setExposurePoint(Offset(0.5, 0.5)); verify(CameraPlatform.instance.setExposurePoint( - cameraController.cameraId, Point(0.5, 0.5))) + cameraController.cameraId, Point(0.5, 0.5))) .called(1); }); test('setExposurePoint() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance.setExposurePoint( + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance.setExposurePoint( cameraController.cameraId, Point(0.5, 0.5))) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.setExposurePoint(Offset(0.5, 0.5)), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposurePoint(Offset(0.5, 0.5)), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('getMinExposureOffset() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -715,38 +717,38 @@ void main() { await cameraController.getMinExposureOffset(); verify(CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId)) + .getMinExposureOffset(cameraController.cameraId)) .called(1); }); test('getMinExposureOffset() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.getMinExposureOffset(), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getMinExposureOffset(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('getMaxExposureOffset() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -760,38 +762,38 @@ void main() { await cameraController.getMaxExposureOffset(); verify(CameraPlatform.instance - .getMaxExposureOffset(cameraController.cameraId)) + .getMaxExposureOffset(cameraController.cameraId)) .called(1); }); test('getMaxExposureOffset() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.getMaxExposureOffset(), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getMaxExposureOffset(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('getExposureOffsetStepSize() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -805,39 +807,39 @@ void main() { await cameraController.getExposureOffsetStepSize(); verify(CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId)) + .getMinExposureOffset(cameraController.cameraId)) .called(1); }); test( 'getExposureOffsetStepSize() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.getExposureOffsetStepSize(), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getExposureOffsetStepSize(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('setExposureOffset() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -848,107 +850,107 @@ void main() { ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId)) + .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => -1.0); when(CameraPlatform.instance - .getMaxExposureOffset(cameraController.cameraId)) + .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => 2.0); when(CameraPlatform.instance - .getExposureOffsetStepSize(cameraController.cameraId)) + .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) async => 1.0); await cameraController.setExposureOffset(1.0); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 1.0)) + .setExposureOffset(cameraController.cameraId, 1.0)) .called(1); }); test('setExposureOffset() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) - .thenAnswer((_) async => -1.0); - when(CameraPlatform.instance + .thenAnswer((_) async => -1.0); + when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) - .thenAnswer((_) async => 2.0); - when(CameraPlatform.instance + .thenAnswer((_) async => 2.0); + when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) - .thenAnswer((_) async => 1.0); - when(CameraPlatform.instance + .thenAnswer((_) async => 1.0); + when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 1.0)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.setExposureOffset(1.0), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposureOffset(1.0), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test( 'setExposureOffset() throws $CameraException when offset is out of bounds', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) - .thenAnswer((_) async => -1.0); - when(CameraPlatform.instance + .thenAnswer((_) async => -1.0); + when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) - .thenAnswer((_) async => 2.0); - when(CameraPlatform.instance + .thenAnswer((_) async => 2.0); + when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) - .thenAnswer((_) async => 1.0); - - expect( - cameraController.setExposureOffset(3.0), - throwsA(isA().having( - (error) => error.description, - 'exposureOffsetOutOfBounds', - 'The provided exposure offset was outside the supported range for this device.', - ))); - expect( - cameraController.setExposureOffset(-2.0), - throwsA(isA().having( - (error) => error.description, - 'exposureOffsetOutOfBounds', - 'The provided exposure offset was outside the supported range for this device.', - ))); + .thenAnswer((_) async => 1.0); + + expect( + cameraController.setExposureOffset(3.0), + throwsA(isA().having( + (error) => error.description, + 'exposureOffsetOutOfBounds', + 'The provided exposure offset was outside the supported range for this device.', + ))); + expect( + cameraController.setExposureOffset(-2.0), + throwsA(isA().having( + (error) => error.description, + 'exposureOffsetOutOfBounds', + 'The provided exposure offset was outside the supported range for this device.', + ))); - await cameraController.setExposureOffset(2.0); - await cameraController.setExposureOffset(-1.0); - await cameraController.setExposureOffset(-0.0); - verify(CameraPlatform.instance + await cameraController.setExposureOffset(2.0); + await cameraController.setExposureOffset(-1.0); + await cameraController.setExposureOffset(-0.0); + verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 2.0)) - .called(1); - verify(CameraPlatform.instance + .called(1); + verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -1.0)) - .called(1); - verify(CameraPlatform.instance + .called(1); + verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.0)) - .called(1); - }); + .called(1); + }); test('setExposureOffset() rounds offset to nearest step', () async { CameraController cameraController = CameraController( @@ -959,16 +961,16 @@ void main() { ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId)) + .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => -1.0); when(CameraPlatform.instance - .getMaxExposureOffset(cameraController.cameraId)) + .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => 1.0); when(CameraPlatform.instance - .getExposureOffsetStepSize(cameraController.cameraId)) + .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) async => 0.4); when(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 1.0)) + .setExposureOffset(cameraController.cameraId, 1.0)) .thenAnswer((_) async => 1.0); await cameraController.setExposureOffset(1.0); @@ -989,19 +991,19 @@ void main() { await cameraController.setExposureOffset(-0.7); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 0.8)) + .setExposureOffset(cameraController.cameraId, 0.8)) .called(3); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, -0.8)) + .setExposureOffset(cameraController.cameraId, -0.8)) .called(3); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 0.0)) + .setExposureOffset(cameraController.cameraId, 0.0)) .called(2); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 0.4)) + .setExposureOffset(cameraController.cameraId, 0.4)) .called(4); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, -0.4)) + .setExposureOffset(cameraController.cameraId, -0.4)) .called(4); }); }); @@ -1015,11 +1017,10 @@ class MockCameraPlatform extends Mock Future.value(mockAvailableCameras); @override - Future createCamera( - CameraDescription description, - ResolutionPreset resolutionPreset, { - bool enableAudio, - }) => + Future createCamera(CameraDescription description, + ResolutionPreset resolutionPreset, { + bool enableAudio, + }) => mockPlatformException ? throw PlatformException(code: 'foo', message: 'bar') : Future.value(mockInitializeCamera); @@ -1037,13 +1038,14 @@ class MockCameraPlatform extends Mock Stream.value(mockOnCameraErrorEvent); @override - Future takePicture(int cameraId) => mockPlatformException - ? throw PlatformException(code: 'foo', message: 'bar') - : Future.value(mockTakePicture); + Future takePicture(int cameraId) => + mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockTakePicture); @override Future startVideoRecording(int cameraId, - {Duration maxVideoDuration}) => + {Duration maxVideoDuration}) => Future.value(mockVideoRecordingXFile); } From e6367bd24f386b4a95bfb265ea2b83f1ba2d5185 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 30 Dec 2020 10:53:04 +0100 Subject: [PATCH 25/31] Code format --- .../plugins/camera/CameraRegionsTest.java | 73 +- packages/camera/camera/test/camera_test.dart | 1000 ++++++++--------- 2 files changed, 535 insertions(+), 538 deletions(-) diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java index 23f302f291c7..89e5b1230dca 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java @@ -1,17 +1,16 @@ package io.flutter.plugins.camera; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + import android.hardware.camera2.params.MeteringRectangle; import android.util.Size; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - @RunWith(RobolectricTestRunner.class) public class CameraRegionsTest { @@ -42,7 +41,7 @@ public void getMeteringRectangleForPoint_should_throw_for_y_lower_bound() { cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 0, -0.5); } - @Test(expected = IllegalStateException.class) + @Test(expected = IllegalStateException.class) public void getMeteringRectangleForPoint_should_throw_for_null_boundaries() { cameraRegions.getMeteringRectangleForPoint(null, 0, -0); } @@ -89,35 +88,35 @@ public void constructor_should_initialize() { assertNull(cr.getAFMeteringRectangle()); } - @Test - public void setAutoExposureMeteringRectangleFromPoint_should_set_aeMeteringRectangle_for_point() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); - cr.setAutoExposureMeteringRectangleFromPoint(0, 0); - assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAEMeteringRectangle()); - } - - @Test - public void resetAutoExposureMeteringRectangle_should_reset_aeMeteringRectangle() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); - cr.setAutoExposureMeteringRectangleFromPoint(0, 0); - assertNotNull(cr.getAEMeteringRectangle()); - cr.resetAutoExposureMeteringRectangle(); - assertNull(cr.getAEMeteringRectangle()); - } - - @Test - public void setAutoFocusMeteringRectangleFromPoint_should_set_afMeteringRectangle_for_point() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); - cr.setAutoFocusMeteringRectangleFromPoint(0, 0); - assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAFMeteringRectangle()); - } - - @Test - public void resetAutoFocusMeteringRectangle_should_reset_afMeteringRectangle() { - CameraRegions cr = new CameraRegions(new Size(100, 50)); - cr.setAutoFocusMeteringRectangleFromPoint(0, 0); - assertNotNull(cr.getAFMeteringRectangle()); - cr.resetAutoFocusMeteringRectangle(); - assertNull(cr.getAFMeteringRectangle()); - } + @Test + public void setAutoExposureMeteringRectangleFromPoint_should_set_aeMeteringRectangle_for_point() { + CameraRegions cr = new CameraRegions(new Size(100, 50)); + cr.setAutoExposureMeteringRectangleFromPoint(0, 0); + assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAEMeteringRectangle()); + } + + @Test + public void resetAutoExposureMeteringRectangle_should_reset_aeMeteringRectangle() { + CameraRegions cr = new CameraRegions(new Size(100, 50)); + cr.setAutoExposureMeteringRectangleFromPoint(0, 0); + assertNotNull(cr.getAEMeteringRectangle()); + cr.resetAutoExposureMeteringRectangle(); + assertNull(cr.getAEMeteringRectangle()); + } + + @Test + public void setAutoFocusMeteringRectangleFromPoint_should_set_afMeteringRectangle_for_point() { + CameraRegions cr = new CameraRegions(new Size(100, 50)); + cr.setAutoFocusMeteringRectangleFromPoint(0, 0); + assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAFMeteringRectangle()); + } + + @Test + public void resetAutoFocusMeteringRectangle_should_reset_afMeteringRectangle() { + CameraRegions cr = new CameraRegions(new Size(100, 50)); + cr.setAutoFocusMeteringRectangleFromPoint(0, 0); + assertNotNull(cr.getAFMeteringRectangle()); + cr.resetAutoFocusMeteringRectangle(); + assertNull(cr.getAFMeteringRectangle()); + } } diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 1725bb14e362..b6478d4a9ba4 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -14,8 +14,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -get mockAvailableCameras => - [ +get mockAvailableCameras => [ CameraDescription( name: 'camBack', lensDirection: CameraLensDirection.back, @@ -28,8 +27,7 @@ get mockAvailableCameras => get mockInitializeCamera => 13; -get mockOnCameraInitializedEvent => - CameraInitializedEvent( +get mockOnCameraInitializedEvent => CameraInitializedEvent( 13, 75, 75, @@ -54,31 +52,31 @@ void main() { group('camera', () { test('debugCheckIsDisposed should not throw assertion error when disposed', - () { - final MockCameraDescription description = MockCameraDescription(); - final CameraController controller = CameraController( - description, - ResolutionPreset.low, - ); + () { + final MockCameraDescription description = MockCameraDescription(); + final CameraController controller = CameraController( + description, + ResolutionPreset.low, + ); - controller.dispose(); + controller.dispose(); - expect(controller.debugCheckIsDisposed, returnsNormally); - }); + expect(controller.debugCheckIsDisposed, returnsNormally); + }); test('debugCheckIsDisposed should throw assertion error when not disposed', - () { - final MockCameraDescription description = MockCameraDescription(); - final CameraController controller = CameraController( - description, - ResolutionPreset.low, - ); - - expect( - () => controller.debugCheckIsDisposed(), - throwsAssertionError, - ); - }); + () { + final MockCameraDescription description = MockCameraDescription(); + final CameraController controller = CameraController( + description, + ResolutionPreset.low, + ); + + expect( + () => controller.debugCheckIsDisposed(), + throwsAssertionError, + ); + }); test('availableCameras() has camera', () async { CameraPlatform.instance = MockCameraPlatform(); @@ -146,32 +144,32 @@ void main() { expect( cameraController.initialize, throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Error description', 'initialize was called on a disposed CameraController', ))); }); test('initialize() throws $CameraException on $PlatformException ', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - mockPlatformException = true; - - expect( - cameraController.initialize, - throwsA(isA().having( - (error) => error.description, - 'foo', - 'bar', - ))); - mockPlatformException = false; - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + mockPlatformException = true; + + expect( + cameraController.initialize, + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); test('prepareForVideoRecording() calls $CameraPlatform ', () async { CameraController cameraController = CameraController( @@ -197,32 +195,32 @@ void main() { expect( cameraController.takePicture(), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController.', 'takePicture was called on uninitialized CameraController', ))); }); test('takePicture() throws $CameraException when takePicture is true', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isTakingPicture: true); - expect( - cameraController.takePicture(), - throwsA(isA().having( - (error) => error.description, - 'Previous capture has not returned yet.', - 'takePicture was called before the previous capture returned.', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isTakingPicture: true); + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'Previous capture has not returned yet.', + 'takePicture was called before the previous capture returned.', + ))); + }); test('takePicture() returns $XFile', () async { CameraController cameraController = CameraController( @@ -238,107 +236,107 @@ void main() { }); test('takePicture() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - mockPlatformException = true; - expect( - cameraController.takePicture(), - throwsA(isA().having( - (error) => error.description, - 'foo', - 'bar', - ))); - mockPlatformException = false; - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + mockPlatformException = true; + expect( + cameraController.takePicture(), + throwsA(isA().having( + (error) => error.description, + 'foo', + 'bar', + ))); + mockPlatformException = false; + }); test('startVideoRecording() throws $CameraException when uninitialized', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController', - 'startVideoRecording was called on uninitialized CameraController', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'startVideoRecording was called on uninitialized CameraController', + ))); + }); test('startVideoRecording() throws $CameraException when recording videos', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isRecordingVideo: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'A video recording is already started.', - 'startVideoRecording was called when a recording is already started.', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isRecordingVideo: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ))); + }); test( 'startVideoRecording() throws $CameraException when already streaming images', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - - cameraController.value = - cameraController.value.copyWith(isStreamingImages: true); - - expect( - cameraController.startVideoRecording(), - throwsA(isA().having( - (error) => error.description, - 'A camera has started streaming images.', - 'startVideoRecording was called while a camera was streaming images.', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + + cameraController.value = + cameraController.value.copyWith(isStreamingImages: true); + + expect( + cameraController.startVideoRecording(), + throwsA(isA().having( + (error) => error.description, + 'A camera has started streaming images.', + 'startVideoRecording was called while a camera was streaming images.', + ))); + }); test('getMaxZoomLevel() throws $CameraException when uninitialized', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.getMaxZoomLevel, - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController', - 'getMaxZoomLevel was called on uninitialized CameraController', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.getMaxZoomLevel, + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'getMaxZoomLevel was called on uninitialized CameraController', + ))); + }); test('getMaxZoomLevel() throws $CameraException when disposed', () async { CameraController cameraController = CameraController( @@ -354,7 +352,7 @@ void main() { expect( cameraController.getMaxZoomLevel, throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController', 'getMaxZoomLevel was called on uninitialized CameraController', ))); @@ -362,31 +360,31 @@ void main() { test( 'getMaxZoomLevel() throws $CameraException when a platform exception occured.', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - when(CameraPlatform.instance.getMaxZoomLevel(mockInitializeCamera)) - .thenThrow(PlatformException( + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance.getMaxZoomLevel(mockInitializeCamera)) + .thenThrow(PlatformException( code: 'TEST_ERROR', message: 'This is a test error messge', details: null)); - expect( - cameraController.getMaxZoomLevel, - throwsA(isA() - .having((error) => error.code, 'code', 'TEST_ERROR') - .having( - (error) => error.description, + expect( + cameraController.getMaxZoomLevel, + throwsA(isA() + .having((error) => error.code, 'code', 'TEST_ERROR') + .having( + (error) => error.description, 'description', 'This is a test error messge', ))); - }); + }); test('getMaxZoomLevel() returns max zoom level.', () async { CameraController cameraController = CameraController( @@ -405,22 +403,22 @@ void main() { }); test('getMinZoomLevel() throws $CameraException when uninitialized', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - expect( - cameraController.getMinZoomLevel, - throwsA(isA().having( - (error) => error.description, - 'Uninitialized CameraController', - 'getMinZoomLevel was called on uninitialized CameraController', - ))); - }); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + cameraController.getMinZoomLevel, + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'getMinZoomLevel was called on uninitialized CameraController', + ))); + }); test('getMinZoomLevel() throws $CameraException when disposed', () async { CameraController cameraController = CameraController( @@ -436,7 +434,7 @@ void main() { expect( cameraController.getMinZoomLevel, throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController', 'getMinZoomLevel was called on uninitialized CameraController', ))); @@ -444,31 +442,31 @@ void main() { test( 'getMinZoomLevel() throws $CameraException when a platform exception occured.', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - when(CameraPlatform.instance.getMinZoomLevel(mockInitializeCamera)) - .thenThrow(PlatformException( + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance.getMinZoomLevel(mockInitializeCamera)) + .thenThrow(PlatformException( code: 'TEST_ERROR', message: 'This is a test error messge', details: null)); - expect( - cameraController.getMinZoomLevel, - throwsA(isA() - .having((error) => error.code, 'code', 'TEST_ERROR') - .having( - (error) => error.description, + expect( + cameraController.getMinZoomLevel, + throwsA(isA() + .having((error) => error.code, 'code', 'TEST_ERROR') + .having( + (error) => error.description, 'description', 'This is a test error messge', ))); - }); + }); test('getMinZoomLevel() returns max zoom level.', () async { CameraController cameraController = CameraController( @@ -495,9 +493,9 @@ void main() { ResolutionPreset.max); expect( - () => cameraController.setZoomLevel(42.0), + () => cameraController.setZoomLevel(42.0), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController', 'setZoomLevel was called on uninitialized CameraController', ))); @@ -515,9 +513,9 @@ void main() { await cameraController.dispose(); expect( - () => cameraController.setZoomLevel(42.0), + () => cameraController.setZoomLevel(42.0), throwsA(isA().having( - (error) => error.description, + (error) => error.description, 'Uninitialized CameraController', 'setZoomLevel was called on uninitialized CameraController', ))); @@ -525,50 +523,50 @@ void main() { test( 'setZoomLevel() throws $CameraException when a platform exception occured.', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - - await cameraController.initialize(); - when(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) - .thenThrow(PlatformException( + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) + .thenThrow(PlatformException( code: 'TEST_ERROR', message: 'This is a test error messge', details: null)); - expect( - () => cameraController.setZoomLevel(42), - throwsA(isA() - .having((error) => error.code, 'code', 'TEST_ERROR') - .having( - (error) => error.description, + expect( + () => cameraController.setZoomLevel(42), + throwsA(isA() + .having((error) => error.code, 'code', 'TEST_ERROR') + .having( + (error) => error.description, 'description', 'This is a test error messge', ))); - reset(CameraPlatform.instance); - }); + reset(CameraPlatform.instance); + }); test( 'setZoomLevel() completes and calls method channel with correct value.', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); - await cameraController.initialize(); - await cameraController.setZoomLevel(42.0); + await cameraController.initialize(); + await cameraController.setZoomLevel(42.0); - verify(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) - .called(1); - }); + verify(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) + .called(1); + }); test('setFlashMode() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -582,38 +580,38 @@ void main() { await cameraController.setFlashMode(FlashMode.always); verify(CameraPlatform.instance - .setFlashMode(cameraController.cameraId, FlashMode.always)) + .setFlashMode(cameraController.cameraId, FlashMode.always)) .called(1); }); test('setFlashMode() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .setFlashMode(cameraController.cameraId, FlashMode.always)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.setFlashMode(FlashMode.always), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setFlashMode(FlashMode.always), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('setExposureMode() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -627,38 +625,38 @@ void main() { await cameraController.setExposureMode(ExposureMode.auto); verify(CameraPlatform.instance - .setExposureMode(cameraController.cameraId, ExposureMode.auto)) + .setExposureMode(cameraController.cameraId, ExposureMode.auto)) .called(1); }); test('setExposureMode() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .setExposureMode(cameraController.cameraId, ExposureMode.auto)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.setExposureMode(ExposureMode.auto), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposureMode(ExposureMode.auto), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('setExposurePoint() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -672,38 +670,38 @@ void main() { await cameraController.setExposurePoint(Offset(0.5, 0.5)); verify(CameraPlatform.instance.setExposurePoint( - cameraController.cameraId, Point(0.5, 0.5))) + cameraController.cameraId, Point(0.5, 0.5))) .called(1); }); test('setExposurePoint() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance.setExposurePoint( + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance.setExposurePoint( cameraController.cameraId, Point(0.5, 0.5))) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.setExposurePoint(Offset(0.5, 0.5)), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposurePoint(Offset(0.5, 0.5)), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('getMinExposureOffset() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -717,38 +715,38 @@ void main() { await cameraController.getMinExposureOffset(); verify(CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId)) + .getMinExposureOffset(cameraController.cameraId)) .called(1); }); test('getMinExposureOffset() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.getMinExposureOffset(), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getMinExposureOffset(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('getMaxExposureOffset() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -762,38 +760,38 @@ void main() { await cameraController.getMaxExposureOffset(); verify(CameraPlatform.instance - .getMaxExposureOffset(cameraController.cameraId)) + .getMaxExposureOffset(cameraController.cameraId)) .called(1); }); test('getMaxExposureOffset() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.getMaxExposureOffset(), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getMaxExposureOffset(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('getExposureOffsetStepSize() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -807,39 +805,39 @@ void main() { await cameraController.getExposureOffsetStepSize(); verify(CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId)) + .getMinExposureOffset(cameraController.cameraId)) .called(1); }); test( 'getExposureOffsetStepSize() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.getExposureOffsetStepSize(), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.getExposureOffsetStepSize(), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test('setExposureOffset() calls $CameraPlatform', () async { CameraController cameraController = CameraController( @@ -850,107 +848,107 @@ void main() { ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId)) + .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => -1.0); when(CameraPlatform.instance - .getMaxExposureOffset(cameraController.cameraId)) + .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => 2.0); when(CameraPlatform.instance - .getExposureOffsetStepSize(cameraController.cameraId)) + .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) async => 1.0); await cameraController.setExposureOffset(1.0); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 1.0)) + .setExposureOffset(cameraController.cameraId, 1.0)) .called(1); }); test('setExposureOffset() throws $CameraException on $PlatformException', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) - .thenAnswer((_) async => -1.0); - when(CameraPlatform.instance + .thenAnswer((_) async => -1.0); + when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) - .thenAnswer((_) async => 2.0); - when(CameraPlatform.instance + .thenAnswer((_) async => 2.0); + when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) - .thenAnswer((_) async => 1.0); - when(CameraPlatform.instance + .thenAnswer((_) async => 1.0); + when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 1.0)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - details: null, - ), - ); - - expect( - cameraController.setExposureOffset(1.0), - throwsA(isA().having( - (error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); - }); + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + details: null, + ), + ); + + expect( + cameraController.setExposureOffset(1.0), + throwsA(isA().having( + (error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); test( 'setExposureOffset() throws $CameraException when offset is out of bounds', - () async { - CameraController cameraController = CameraController( - CameraDescription( - name: 'cam', - lensDirection: CameraLensDirection.back, - sensorOrientation: 90), - ResolutionPreset.max); - await cameraController.initialize(); - when(CameraPlatform.instance + () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) - .thenAnswer((_) async => -1.0); - when(CameraPlatform.instance + .thenAnswer((_) async => -1.0); + when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) - .thenAnswer((_) async => 2.0); - when(CameraPlatform.instance + .thenAnswer((_) async => 2.0); + when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) - .thenAnswer((_) async => 1.0); - - expect( - cameraController.setExposureOffset(3.0), - throwsA(isA().having( - (error) => error.description, - 'exposureOffsetOutOfBounds', - 'The provided exposure offset was outside the supported range for this device.', - ))); - expect( - cameraController.setExposureOffset(-2.0), - throwsA(isA().having( - (error) => error.description, - 'exposureOffsetOutOfBounds', - 'The provided exposure offset was outside the supported range for this device.', - ))); + .thenAnswer((_) async => 1.0); - await cameraController.setExposureOffset(2.0); - await cameraController.setExposureOffset(-1.0); - await cameraController.setExposureOffset(-0.0); - verify(CameraPlatform.instance + expect( + cameraController.setExposureOffset(3.0), + throwsA(isA().having( + (error) => error.description, + 'exposureOffsetOutOfBounds', + 'The provided exposure offset was outside the supported range for this device.', + ))); + expect( + cameraController.setExposureOffset(-2.0), + throwsA(isA().having( + (error) => error.description, + 'exposureOffsetOutOfBounds', + 'The provided exposure offset was outside the supported range for this device.', + ))); + + await cameraController.setExposureOffset(2.0); + await cameraController.setExposureOffset(-1.0); + await cameraController.setExposureOffset(-0.0); + verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 2.0)) - .called(1); - verify(CameraPlatform.instance + .called(1); + verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -1.0)) - .called(1); - verify(CameraPlatform.instance + .called(1); + verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.0)) - .called(1); - }); + .called(1); + }); test('setExposureOffset() rounds offset to nearest step', () async { CameraController cameraController = CameraController( @@ -961,16 +959,16 @@ void main() { ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance - .getMinExposureOffset(cameraController.cameraId)) + .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => -1.0); when(CameraPlatform.instance - .getMaxExposureOffset(cameraController.cameraId)) + .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => 1.0); when(CameraPlatform.instance - .getExposureOffsetStepSize(cameraController.cameraId)) + .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) async => 0.4); when(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 1.0)) + .setExposureOffset(cameraController.cameraId, 1.0)) .thenAnswer((_) async => 1.0); await cameraController.setExposureOffset(1.0); @@ -991,19 +989,19 @@ void main() { await cameraController.setExposureOffset(-0.7); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 0.8)) + .setExposureOffset(cameraController.cameraId, 0.8)) .called(3); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, -0.8)) + .setExposureOffset(cameraController.cameraId, -0.8)) .called(3); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 0.0)) + .setExposureOffset(cameraController.cameraId, 0.0)) .called(2); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, 0.4)) + .setExposureOffset(cameraController.cameraId, 0.4)) .called(4); verify(CameraPlatform.instance - .setExposureOffset(cameraController.cameraId, -0.4)) + .setExposureOffset(cameraController.cameraId, -0.4)) .called(4); }); }); @@ -1017,10 +1015,11 @@ class MockCameraPlatform extends Mock Future.value(mockAvailableCameras); @override - Future createCamera(CameraDescription description, - ResolutionPreset resolutionPreset, { - bool enableAudio, - }) => + Future createCamera( + CameraDescription description, + ResolutionPreset resolutionPreset, { + bool enableAudio, + }) => mockPlatformException ? throw PlatformException(code: 'foo', message: 'bar') : Future.value(mockInitializeCamera); @@ -1038,14 +1037,13 @@ class MockCameraPlatform extends Mock Stream.value(mockOnCameraErrorEvent); @override - Future takePicture(int cameraId) => - mockPlatformException - ? throw PlatformException(code: 'foo', message: 'bar') - : Future.value(mockTakePicture); + Future takePicture(int cameraId) => mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockTakePicture); @override Future startVideoRecording(int cameraId, - {Duration maxVideoDuration}) => + {Duration maxVideoDuration}) => Future.value(mockVideoRecordingXFile); } From 971b4e676a4a7542360435e9d1bb39d3e587f231 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 7 Jan 2021 17:13:18 +0100 Subject: [PATCH 26/31] Fixed compilation error --- .../io/flutter/plugins/camera/Camera.java | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index a99e927ce4b7..51c01de1224a 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -771,11 +771,14 @@ public void setExposurePoint(@NonNull final Result result, Double x, Double y) public void setFocusMode(@NonNull final Result result, FocusMode mode) throws CameraAccessException { this.focusMode = mode; - initPreviewCaptureBuilder(); + + updateFocus(mode); + switch (mode) { case auto: - cameraCaptureSession.setRepeatingRequest( - captureRequestBuilder.build(), pictureCaptureCallback, null); + refreshPreviewCaptureSession( + null, + (code, message) -> result.error("setFocusMode", message, null)); break; case locked: lockAutoFocus( @@ -800,35 +803,22 @@ public void setFocusPoint(@NonNull final Result result, Double x, Double y) result.error("setFocusPointFailed", "Device does not have focus point capabilities", null); return; } + // Check if the current region boundaries are known if (cameraRegions.getMaxBoundaries() == null) { result.error("setFocusPointFailed", "Could not determine max region boundaries", null); return; } + // Set the metering rectangle - if (x == null || y == null) cameraRegions.resetAutoFocusMeteringRectangle(); - else cameraRegions.setAutoFocusMeteringRectangleFromPoint(x, y); - // Apply the new metering rectangle - initPreviewCaptureBuilder(); - switch (focusMode) { - case auto: - cameraCaptureSession.setRepeatingRequest( - captureRequestBuilder.build(), pictureCaptureCallback, null); - break; - case locked: - lockAutoFocus( - new CaptureCallback() { - @Override - public void onCaptureCompleted( - @NonNull CameraCaptureSession session, - @NonNull CaptureRequest request, - @NonNull TotalCaptureResult result) { - unlockAutoFocus(); - } - }); - break; + if (x == null || y == null) { + cameraRegions.resetAutoFocusMeteringRectangle(); + } else { + cameraRegions.setAutoFocusMeteringRectangleFromPoint(x, y); } - result.success(null); + + // Apply the new metering rectangle + setFocusMode(result, focusMode); } @TargetApi(VERSION_CODES.P) From 6d174fb473eeee26a62db7b7441bc61c0bd2dcb7 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 7 Jan 2021 19:57:22 +0100 Subject: [PATCH 27/31] Fix formatting --- .../src/main/java/io/flutter/plugins/camera/Camera.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 51c01de1224a..8ecf9dcf8641 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -777,8 +777,7 @@ public void setFocusMode(@NonNull final Result result, FocusMode mode) switch (mode) { case auto: refreshPreviewCaptureSession( - null, - (code, message) -> result.error("setFocusMode", message, null)); + null, (code, message) -> result.error("setFocusMode", message, null)); break; case locked: lockAutoFocus( From 5d8c23521cbb05d31e3bad09938e6b35a6dcb912 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 11 Jan 2021 09:26:15 +0100 Subject: [PATCH 28/31] Add missing license headers to java source files. --- .../src/main/java/io/flutter/plugins/camera/Camera.java | 4 ++++ .../java/io/flutter/plugins/camera/CameraPermissions.java | 4 ++++ .../main/java/io/flutter/plugins/camera/CameraRegions.java | 4 ++++ .../src/main/java/io/flutter/plugins/camera/CameraUtils.java | 4 ++++ .../src/main/java/io/flutter/plugins/camera/CameraZoom.java | 4 ++++ .../main/java/io/flutter/plugins/camera/DartMessenger.java | 4 ++++ .../java/io/flutter/plugins/camera/MethodCallHandlerImpl.java | 4 ++++ .../java/io/flutter/plugins/camera/PictureCaptureRequest.java | 4 ++++ .../io/flutter/plugins/camera/media/MediaRecorderBuilder.java | 1 + .../java/io/flutter/plugins/camera/types/ExposureMode.java | 4 ++++ .../main/java/io/flutter/plugins/camera/types/FlashMode.java | 4 ++++ .../main/java/io/flutter/plugins/camera/types/FocusMode.java | 4 ++++ .../io/flutter/plugins/camera/types/ResolutionPreset.java | 4 ++++ .../java/io/flutter/plugins/camera/CameraPermissionsTest.java | 4 ++++ .../java/io/flutter/plugins/camera/CameraRegionsTest.java | 4 ++++ .../test/java/io/flutter/plugins/camera/CameraZoomTest.java | 4 ++++ .../java/io/flutter/plugins/camera/DartMessengerTest.java | 4 ++++ .../io/flutter/plugins/camera/PictureCaptureRequestTest.java | 4 ++++ .../plugins/camera/media/MediaRecorderBuilderTest.java | 4 ++++ .../io/flutter/plugins/camera/types/ExposureModeTest.java | 4 ++++ .../java/io/flutter/plugins/camera/types/FlashModeTest.java | 4 ++++ .../java/io/flutter/plugins/camera/types/FocusModeTest.java | 4 ++++ 22 files changed, 85 insertions(+) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index c0517217f611..f1a8eeb40d64 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import static android.view.OrientationEventListener.ORIENTATION_UNKNOWN; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java index b4569d2fec07..3529e69a2b0b 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import android.Manifest; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java index 2d244b7f70d7..04412a56631f 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import android.hardware.camera2.params.MeteringRectangle; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java index 3b665d6b24f2..6f04dc80e102 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import android.app.Activity; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java index a179f12db224..5eed9f4734b7 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import android.graphics.Rect; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 120e871abb6c..ec68ac0acda3 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import android.text.TextUtils; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index c7636d5c5edb..a587cc42b61d 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import android.app.Activity; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java index 1103b8583ad6..189f2f1490dc 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import androidx.annotation.Nullable; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java index b2309c83a4a5..4c3fb3add230 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java @@ -1,6 +1,7 @@ // Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + package io.flutter.plugins.camera.media; import android.media.CamcorderProfile; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java index 8066f59d2b14..595206fa2216 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera.types; // Mirrors exposure_mode.dart diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java index ee6fe489511f..c4f0998c418a 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera.types; // Mirrors flash_mode.dart diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java index 10494f26a904..b0dba047f7eb 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera.types; // Mirrors focus_mode.dart diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java index ffbe2e62095d..1508dcefb293 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera.types; // Mirrors camera.dart diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java index b622c313258a..2b19b5dbb0d6 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import static junit.framework.TestCase.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java index 89e5b1230dca..99745e56a857 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import static org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java index 93aaa5d926b4..8f05da71b5c5 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import static org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index a7a8f930c42d..64425b7b8283 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import static junit.framework.TestCase.assertNull; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java index 2356b306c6c4..3ede0b7abe3a 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera; import static org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java index 622b49b660a2..823975803994 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera.media; import static org.junit.Assert.assertNotNull; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java index 28d2343cedcd..63810f0b5684 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera.types; import static org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java index bba01836545a..1f5f0c6272ed 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera.types; import static org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java index 795a9397a735..4aa6fadf776b 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + package io.flutter.plugins.camera.types; import static org.junit.Assert.assertEquals; From 0ba1c9023d402a09997160e06f0d03fe27275be2 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 11 Jan 2021 09:32:11 +0100 Subject: [PATCH 29/31] Update platform interface dependency --- packages/camera/camera/pubspec.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 3c84551df9fd..6d7e3c0e0dbb 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -8,9 +8,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - #TODO(BeMacized): Replace with reference to pub.dev version once updated platform interface has been published. - camera_platform_interface: - path: ../camera_platform_interface + camera_platform_interface: ^1.5.0 pedantic: ^1.8.0 dev_dependencies: From a6f44420e63c1a32a152150c05041b6a5a86f694 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 11 Jan 2021 11:51:33 +0100 Subject: [PATCH 30/31] Change fps range determination --- .../android/src/main/java/io/flutter/plugins/camera/Camera.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index f1a8eeb40d64..bf3cf86198db 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -165,7 +165,7 @@ private void initFps(CameraCharacteristics cameraCharacteristics) { int upper = range.getUpper(); Log.i("Camera", "[FPS Range Available] is:" + range); if (upper >= 10) { - if (fpsRange == null || upper < fpsRange.getUpper()) { + if (fpsRange == null || upper > fpsRange.getUpper()) { fpsRange = range; } } From bcacd4bd975fc1adc83ae61c1224419e0571f9c4 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 11 Jan 2021 15:12:21 +0100 Subject: [PATCH 31/31] Fix analysis warnings --- packages/camera/camera/example/lib/main.dart | 24 +++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 09edb26553f7..681a45172816 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -339,6 +339,7 @@ class _CameraExampleHomeState extends State ? Colors.orange : Colors.blue, ); + return SizeTransition( sizeFactor: _exposureModeControlRowAnimation, child: ClipRect( @@ -404,6 +405,17 @@ class _CameraExampleHomeState extends State } Widget _focusModeControlRowWidget() { + final ButtonStyle styleAuto = TextButton.styleFrom( + primary: controller?.value?.focusMode == FocusMode.auto + ? Colors.orange + : Colors.blue, + ); + final ButtonStyle styleLocked = TextButton.styleFrom( + primary: controller?.value?.focusMode == FocusMode.locked + ? Colors.orange + : Colors.blue, + ); + return SizeTransition( sizeFactor: _focusModeControlRowAnimation, child: ClipRect( @@ -418,11 +430,9 @@ class _CameraExampleHomeState extends State mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.max, children: [ - FlatButton( + TextButton( child: Text('AUTO'), - textColor: controller?.value?.focusMode == FocusMode.auto - ? Colors.orange - : Colors.blue, + style: styleAuto, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.auto) : null, @@ -431,11 +441,9 @@ class _CameraExampleHomeState extends State showInSnackBar('Resetting focus point'); }, ), - FlatButton( + TextButton( child: Text('LOCKED'), - textColor: controller?.value?.focusMode == FocusMode.locked - ? Colors.orange - : Colors.blue, + style: styleLocked, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.locked) : null,