From 65f9644af543154605569e62af09bf6dca85fe96 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Fri, 18 Dec 2020 21:43:36 +0100 Subject: [PATCH 1/2] Refactored and tested zoom on Android --- packages/camera/camera/CHANGELOG.md | 4 + packages/camera/camera/android/build.gradle | 10 +- .../io/flutter/plugins/camera/Camera.java | 55 +++- .../io/flutter/plugins/camera/CameraZoom.java | 48 ++++ .../plugins/camera/MethodCallHandlerImpl.java | 43 +++ .../plugins/camera/CameraZoomTest.java | 121 +++++++++ .../camera/example/android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/camera/camera/example/lib/main.dart | 35 ++- .../camera/camera/ios/Classes/CameraPlugin.m | 61 +++++ .../camera/lib/src/camera_controller.dart | 52 ++++ packages/camera/camera/pubspec.yaml | 4 +- packages/camera/camera/test/camera_test.dart | 248 ++++++++++++++++++ 13 files changed, 663 insertions(+), 22 deletions(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 8a7c979c3b72..43f21839a020 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.1 + +* Add zoom support for Android and iOS implementations. + ## 0.6.0+1 Updated README to inform users that iOS 10.0+ is needed for use diff --git a/packages/camera/camera/android/build.gradle b/packages/camera/camera/android/build.gradle index d19d6df7b447..0b88fd10fb71 100644 --- a/packages/camera/camera/android/build.gradle +++ b/packages/camera/camera/android/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.android.tools.build:gradle:3.5.0' } } @@ -40,16 +40,16 @@ android { sourceCompatibility = '1.8' targetCompatibility = '1.8' } - dependencies { - implementation 'androidx.annotation:annotation:1.0.0' - implementation 'androidx.core:core:1.0.0' - } testOptions { + unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true } } dependencies { + compileOnly 'androidx.annotation:annotation:1.1.0' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:3.5.13' + testImplementation 'androidx.test:core:1.3.0' + testImplementation 'org.robolectric:robolectric:4.3' } 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 306dd447cfb9..95bc6e00dda5 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 @@ -8,6 +8,7 @@ import android.app.Activity; import android.content.Context; import android.graphics.ImageFormat; +import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; @@ -19,7 +20,6 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; -import android.hardware.camera2.params.StreamConfigurationMap; import android.media.CamcorderProfile; import android.media.Image; import android.media.ImageReader; @@ -43,6 +43,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.Executors; @@ -56,19 +57,20 @@ public class Camera { 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 DartMessenger dartMessenger; private CaptureRequest.Builder captureRequestBuilder; private MediaRecorder mediaRecorder; private boolean recordingVideo; private File videoRecordingFile; - private CamcorderProfile recordingProfile; private int currentOrientation = ORIENTATION_UNKNOWN; - private Context applicationContext; // Mirrors camera.dart public enum ResolutionPreset { @@ -111,11 +113,7 @@ public void onOrientationChanged(int i) { orientationEventListener.enable(); CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); - StreamConfigurationMap streamConfigurationMap = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - //noinspection ConstantConditions sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - //noinspection ConstantConditions isFrontFacing = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT; ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset); @@ -123,6 +121,10 @@ public void onOrientationChanged(int i) { 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 { @@ -215,10 +217,6 @@ private void writeToFile(ByteBuffer buffer, File file) throws IOException { } } - SurfaceTextureEntry getFlutterTexture() { - return flutterTexture; - } - public void takePicture(@NonNull final Result result) { final File outputDir = applicationContext.getCacheDir(); final File file; @@ -509,6 +507,39 @@ private void setImageStreamImageAvailableListener(final EventChannel.EventSink i 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(); 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 new file mode 100644 index 000000000000..a179f12db224 --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java @@ -0,0 +1,48 @@ +package io.flutter.plugins.camera; + +import android.graphics.Rect; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.math.MathUtils; + +public final class CameraZoom { + public static final float DEFAULT_ZOOM_FACTOR = 1.0f; + + @NonNull private final Rect cropRegion = new Rect(); + @Nullable private final Rect sensorSize; + + public final float maxZoom; + public final boolean hasSupport; + + public CameraZoom(@Nullable final Rect sensorArraySize, final Float maxZoom) { + this.sensorSize = sensorArraySize; + + if (this.sensorSize == null) { + this.maxZoom = DEFAULT_ZOOM_FACTOR; + this.hasSupport = false; + return; + } + + this.maxZoom = + ((maxZoom == null) || (maxZoom < DEFAULT_ZOOM_FACTOR)) ? DEFAULT_ZOOM_FACTOR : maxZoom; + + this.hasSupport = (Float.compare(this.maxZoom, DEFAULT_ZOOM_FACTOR) > 0); + } + + public Rect computeZoom(final float zoom) { + if (sensorSize == null || !this.hasSupport) { + return null; + } + + final float newZoom = MathUtils.clamp(zoom, DEFAULT_ZOOM_FACTOR, this.maxZoom); + + final int centerX = this.sensorSize.width() / 2; + final int centerY = this.sensorSize.height() / 2; + final int deltaX = (int) ((0.5f * this.sensorSize.width()) / newZoom); + final int deltaY = (int) ((0.5f * this.sensorSize.height()) / newZoom); + + this.cropRegion.set(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY); + + return cropRegion; + } +} 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 6c2e65e76f9e..ef29815ce32c 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 @@ -142,6 +142,49 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } break; } + case "getMaxZoomLevel": + { + assert camera != null; + + try { + float maxZoomLevel = camera.getMaxZoomLevel(); + result.success(maxZoomLevel); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "getMinZoomLevel": + { + assert camera != null; + + try { + float minZoomLevel = camera.getMinZoomLevel(); + result.success(minZoomLevel); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "setZoomLevel": + { + assert camera != null; + + Double zoom = call.argument("zoom"); + + if (zoom == null) { + result.error( + "ZOOM_ERROR", "setZoomLevel is called without specifying a zoom level.", null); + return; + } + + try { + camera.setZoomLevel(result, zoom.floatValue()); + } catch (Exception e) { + handleException(e, result); + } + break; + } case "dispose": { if (camera != null) { 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 new file mode 100644 index 000000000000..93aaa5d926b4 --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java @@ -0,0 +1,121 @@ +package io.flutter.plugins.camera; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.graphics.Rect; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class CameraZoomTest { + + @Test + public void ctor_when_parameters_are_valid() { + final Rect sensorSize = new Rect(0, 0, 0, 0); + final Float maxZoom = 4.0f; + final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom); + + assertNotNull(cameraZoom); + assertTrue(cameraZoom.hasSupport); + assertEquals(4.0f, cameraZoom.maxZoom, 0); + assertEquals(1.0f, CameraZoom.DEFAULT_ZOOM_FACTOR, 0); + } + + @Test + public void ctor_when_sensor_size_is_null() { + final Rect sensorSize = null; + final Float maxZoom = 4.0f; + final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom); + + assertNotNull(cameraZoom); + assertFalse(cameraZoom.hasSupport); + assertEquals(cameraZoom.maxZoom, 1.0f, 0); + } + + @Test + public void ctor_when_max_zoom_is_null() { + final Rect sensorSize = new Rect(0, 0, 0, 0); + final Float maxZoom = null; + final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom); + + assertNotNull(cameraZoom); + assertFalse(cameraZoom.hasSupport); + assertEquals(cameraZoom.maxZoom, 1.0f, 0); + } + + @Test + public void ctor_when_max_zoom_is_smaller_then_default_zoom_factor() { + final Rect sensorSize = new Rect(0, 0, 0, 0); + final Float maxZoom = 0.5f; + final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom); + + assertNotNull(cameraZoom); + assertFalse(cameraZoom.hasSupport); + assertEquals(cameraZoom.maxZoom, 1.0f, 0); + } + + @Test + public void setZoom_when_no_support_should_not_set_scaler_crop_region() { + final CameraZoom cameraZoom = new CameraZoom(null, null); + final Rect computedZoom = cameraZoom.computeZoom(2f); + + assertNull(computedZoom); + } + + @Test + public void setZoom_when_sensor_size_equals_zero_should_return_crop_region_of_zero() { + final Rect sensorSize = new Rect(0, 0, 0, 0); + final CameraZoom cameraZoom = new CameraZoom(sensorSize, 20f); + final Rect computedZoom = cameraZoom.computeZoom(18f); + + assertNotNull(computedZoom); + assertEquals(computedZoom.left, 0); + assertEquals(computedZoom.top, 0); + assertEquals(computedZoom.right, 0); + assertEquals(computedZoom.bottom, 0); + } + + @Test + public void setZoom_when_sensor_size_is_valid_should_return_crop_region() { + final Rect sensorSize = new Rect(0, 0, 100, 100); + final CameraZoom cameraZoom = new CameraZoom(sensorSize, 20f); + final Rect computedZoom = cameraZoom.computeZoom(18f); + + assertNotNull(computedZoom); + assertEquals(computedZoom.left, 48); + assertEquals(computedZoom.top, 48); + assertEquals(computedZoom.right, 52); + assertEquals(computedZoom.bottom, 52); + } + + @Test + public void setZoom_when_zoom_is_greater_then_max_zoom_clamp_to_max_zoom() { + final Rect sensorSize = new Rect(0, 0, 100, 100); + final CameraZoom cameraZoom = new CameraZoom(sensorSize, 10f); + final Rect computedZoom = cameraZoom.computeZoom(25f); + + assertNotNull(computedZoom); + assertEquals(computedZoom.left, 45); + assertEquals(computedZoom.top, 45); + assertEquals(computedZoom.right, 55); + assertEquals(computedZoom.bottom, 55); + } + + @Test + public void setZoom_when_zoom_is_smaller_then_min_zoom_clamp_to_min_zoom() { + final Rect sensorSize = new Rect(0, 0, 100, 100); + final CameraZoom cameraZoom = new CameraZoom(sensorSize, 10f); + final Rect computedZoom = cameraZoom.computeZoom(0.5f); + + assertNotNull(computedZoom); + assertEquals(computedZoom.left, 0); + assertEquals(computedZoom.top, 0); + assertEquals(computedZoom.right, 100); + assertEquals(computedZoom.bottom, 100); + } +} diff --git a/packages/camera/camera/example/android/build.gradle b/packages/camera/camera/example/android/build.gradle index 112aa2a87c27..498448e78692 100644 --- a/packages/camera/camera/example/android/build.gradle +++ b/packages/camera/camera/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.android.tools.build:gradle:3.5.0' } } diff --git a/packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties index 019065d1d650..01a286e96a21 100644 --- a/packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index e1edc1b06386..1c1f1b23e48a 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -42,6 +42,13 @@ class _CameraExampleHomeState extends State VideoPlayerController videoController; VoidCallback videoPlayerListener; bool enableAudio = true; + double _minAvailableZoom; + double _maxAvailableZoom; + double _currentScale = 1.0; + double _baseScale = 1.0; + + // Counting pointers (number of user fingers on screen) + int _pointers = 0; @override void initState() { @@ -131,11 +138,35 @@ class _CameraExampleHomeState extends State } else { return AspectRatio( aspectRatio: controller.value.aspectRatio, - child: CameraPreview(controller), + child: Listener( + onPointerDown: (_) => _pointers++, + onPointerUp: (_) => _pointers--, + child: GestureDetector( + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + child: CameraPreview(controller), + ), + ), ); } } + void _handleScaleStart(ScaleStartDetails details) { + _baseScale = _currentScale; + } + + Future _handleScaleUpdate(ScaleUpdateDetails details) async { + // When there are not exactly two fingers on screen don't scale + if (_pointers != 2) { + return; + } + + _currentScale = (_baseScale * details.scale) + .clamp(_minAvailableZoom, _maxAvailableZoom); + + await controller.setZoomLevel(_currentScale); + } + /// Toggle recording audio Widget _toggleAudioWidget() { return Padding( @@ -295,6 +326,8 @@ class _CameraExampleHomeState extends State try { await controller.initialize(); + _maxAvailableZoom = await controller.getMaxZoomLevel(); + _minAvailableZoom = await controller.getMinZoomLevel(); } on CameraException catch (e) { _showCameraException(e); } diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 9455375b8524..47841bdccb8a 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -677,6 +677,60 @@ - (void)stopImageStream { } } +- (void)getMaxZoomLevelWithResult:(FlutterResult)result { + CGFloat maxZoomFactor = [self getMaxAvailableZoomFactor]; + + result([NSNumber numberWithFloat:maxZoomFactor]); +} + +- (void)getMinZoomLevelWithResult:(FlutterResult)result { + CGFloat minZoomFactor = [self getMinAvailableZoomFactor]; + + result([NSNumber numberWithFloat:minZoomFactor]); +} + +- (void)setZoomLevel:(CGFloat)zoom Result:(FlutterResult)result { + CGFloat maxAvailableZoomFactor = [self getMaxAvailableZoomFactor]; + CGFloat minAvailableZoomFactor = [self getMinAvailableZoomFactor]; + + if (maxAvailableZoomFactor < zoom || minAvailableZoomFactor > zoom) { + NSString *errorMessage = [NSString + stringWithFormat:@"Zoom level out of bounds (zoom level should be between %f and %f).", + minAvailableZoomFactor, maxAvailableZoomFactor]; + FlutterError *error = [FlutterError errorWithCode:@"ZOOM_ERROR" + message:errorMessage + details:nil]; + result(error); + return; + } + + NSError *error = nil; + if (![_captureDevice lockForConfiguration:&error]) { + result(getFlutterError(error)); + return; + } + [_captureDevice rampToVideoZoomFactor:zoom withRate:1]; + [_captureDevice unlockForConfiguration]; + + result(nil); +} + +- (CGFloat)getMinAvailableZoomFactor { + if (@available(iOS 11.0, *)) { + return _captureDevice.minAvailableVideoZoomFactor; + } else { + return 1.0; + } +} + +- (CGFloat)getMaxAvailableZoomFactor { + if (@available(iOS 11.0, *)) { + return _captureDevice.maxAvailableVideoZoomFactor; + } else { + return _captureDevice.activeFormat.videoMaxZoomFactor; + } +} + - (BOOL)setupWriterForPath:(NSString *)path { NSError *error = nil; NSURL *outputURL; @@ -910,6 +964,13 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera pauseVideoRecordingWithResult:result]; } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { [_camera resumeVideoRecordingWithResult:result]; + } else if ([@"getMaxZoomLevel" isEqualToString:call.method]) { + [_camera getMaxZoomLevelWithResult:result]; + } else if ([@"getMinZoomLevel" isEqualToString:call.method]) { + [_camera getMinZoomLevelWithResult:result]; + } else if ([@"setZoomLevel" isEqualToString:call.method]) { + CGFloat zoom = ((NSNumber *)argsMap[@"zoom"]).floatValue; + [_camera setZoomLevel:zoom Result:result]; } else { result(FlutterMethodNotImplemented); } diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index fcf00245ce7f..6f52bbe16df7 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -468,6 +468,58 @@ class CameraController extends ValueNotifier { } } + /// Gets the maximum supported zoom level for the selected camera. + Future getMaxZoomLevel() { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'getMaxZoomLevel was called on uninitialized CameraController', + ); + } + + try { + return CameraPlatform.instance.getMaxZoomLevel(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the minimum supported zoom level for the selected camera. + Future getMinZoomLevel() { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'getMinZoomLevel was called on uninitialized CameraController', + ); + } + + try { + return CameraPlatform.instance.getMinZoomLevel(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// 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` + /// when an illegal zoom level is suplied. + Future setZoomLevel(double zoom) { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController', + 'setZoomLevel was called on uninitialized CameraController', + ); + } + + try { + return CameraPlatform.instance.setZoomLevel(_cameraId, zoom); + } 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 cc25133f95f9..3194fff33684 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -2,13 +2,13 @@ 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.0+1 +version: 0.6.1 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - camera_platform_interface: ^1.0.0 + camera_platform_interface: ^1.0.4 dev_dependencies: path_provider: ^0.5.0 diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index b129849cd141..fbefabc4da7c 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -310,6 +310,254 @@ void main() { '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', + ))); + }); + + test('getMaxZoomLevel() throws $CameraException when disposed', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + await cameraController.dispose(); + + expect( + cameraController.getMaxZoomLevel, + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'getMaxZoomLevel was called on uninitialized CameraController', + ))); + }); + + 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( + 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, + 'description', + 'This is a test error messge', + ))); + }); + + test('getMaxZoomLevel() returns max zoom level.', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance.getMaxZoomLevel(mockInitializeCamera)) + .thenAnswer((_) => Future.value(42.0)); + + final maxZoomLevel = await cameraController.getMaxZoomLevel(); + expect(maxZoomLevel, 42.0); + }); + + 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', + ))); + }); + + test('getMinZoomLevel() throws $CameraException when disposed', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + await cameraController.dispose(); + + expect( + cameraController.getMinZoomLevel, + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'getMinZoomLevel was called on uninitialized CameraController', + ))); + }); + + 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( + 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, + 'description', + 'This is a test error messge', + ))); + }); + + test('getMinZoomLevel() returns max zoom level.', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance.getMinZoomLevel(mockInitializeCamera)) + .thenAnswer((_) => Future.value(42.0)); + + final maxZoomLevel = await cameraController.getMinZoomLevel(); + expect(maxZoomLevel, 42.0); + }); + + test('setZoomLevel() throws $CameraException when uninitialized', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + expect( + () => cameraController.setZoomLevel(42.0), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'setZoomLevel was called on uninitialized CameraController', + ))); + }); + + test('setZoomLevel() throws $CameraException when disposed', () async { + CameraController cameraController = CameraController( + CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + await cameraController.dispose(); + + expect( + () => cameraController.setZoomLevel(42.0), + throwsA(isA().having( + (error) => error.description, + 'Uninitialized CameraController', + 'setZoomLevel was called on uninitialized CameraController', + ))); + }); + + 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( + 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, + 'description', + 'This is a test error messge', + ))); + + 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); + + await cameraController.initialize(); + await cameraController.setZoomLevel(42.0); + + verify(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) + .called(1); + }); }); } From ee00f702d16ad884be99eaf2c4846aeefb0caa50 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Sat, 19 Dec 2020 12:27:19 +0100 Subject: [PATCH 2/2] Fix merge conflict --- .../android/src/main/java/io/flutter/plugins/camera/Camera.java | 1 - 1 file changed, 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 d0dce69e5b5a..3e8bbc7b295b 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 @@ -75,7 +75,6 @@ public class Camera { private boolean recordingVideo; private File videoRecordingFile; private int currentOrientation = ORIENTATION_UNKNOWN; - private Context applicationContext; private FlashMode flashMode; private PictureCaptureRequest pictureCaptureRequest;