diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 46bd41af128..72bdb025af9 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.9+15 + +* Converts Dart to native platform calls to Pigeon. + ## 0.10.9+14 * Converts native to Dart platform calls to Pigeon. diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java index 7668ec97eec..29459438d50 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -39,8 +39,6 @@ import io.flutter.BuildConfig; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.features.CameraFeature; import io.flutter.plugins.camera.features.CameraFeatureFactory; import io.flutter.plugins.camera.features.CameraFeatures; @@ -68,7 +66,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.concurrent.Executors; @@ -83,23 +80,13 @@ class Camera ImageReader.OnImageAvailableListener { private static final String TAG = "Camera"; - private static final HashMap supportedImageFormats; - - // Current supported outputs. - static { - supportedImageFormats = new HashMap<>(); - supportedImageFormats.put("yuv420", ImageFormat.YUV_420_888); - supportedImageFormats.put("jpeg", ImageFormat.JPEG); - supportedImageFormats.put("nv21", ImageFormat.NV21); - } - /** * Holds all of the camera features/settings and will be used to update the request builder when * one changes. */ CameraFeatures cameraFeatures; - private String imageFormatGroup; + private int imageFormatGroup; /** * Takes an input/output surface and orients the recording correctly. This is needed because @@ -148,7 +135,7 @@ class Camera /** Holds the last known capture properties */ private CameraCaptureProperties captureProps; - MethodChannel.Result flutterResult; + Messages.Result flutterResult; /** A CameraDeviceWrapper implementation that forwards calls to a CameraDevice. */ private class DefaultCameraDeviceWrapper implements CameraDeviceWrapper { @@ -340,7 +327,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { } @SuppressLint("MissingPermission") - public void open(String imageFormatGroup) throws CameraAccessException { + public void open(Integer imageFormatGroup) throws CameraAccessException { this.imageFormatGroup = imageFormatGroup; final ResolutionFeature resolutionFeature = cameraFeatures.getResolution(); @@ -363,17 +350,11 @@ public void open(String imageFormatGroup) throws CameraAccessException { ImageFormat.JPEG, 1); - // For image streaming, use the provided image format or fall back to YUV420. - Integer imageFormat = supportedImageFormats.get(imageFormatGroup); - if (imageFormat == null) { - Log.w(TAG, "The selected imageFormatGroup is not supported by Android. Defaulting to yuv420"); - imageFormat = ImageFormat.YUV_420_888; - } imageStreamReader = new ImageStreamReader( resolutionFeature.getPreviewSize().getWidth(), resolutionFeature.getPreviewSize().getHeight(), - imageFormat, + this.imageFormatGroup, 1); // Open the camera. @@ -618,10 +599,12 @@ private void startCapture(boolean record, boolean stream) throws CameraAccessExc CameraDevice.TEMPLATE_RECORD, successCallback, surfaces.toArray(new Surface[0])); } - public void takePicture(@NonNull final Result result) { + public void takePicture(@NonNull final Messages.Result result) { // Only take one picture at a time. if (cameraCaptureCallback.getCameraState() != CameraState.STATE_PREVIEW) { - result.error("captureAlreadyActive", "Picture is currently already being captured", null); + result.error( + new Messages.FlutterError( + "captureAlreadyActive", "Picture is currently already being captured", null)); return; } @@ -825,9 +808,8 @@ void unlockAutoFocus() { dartMessenger.error(flutterResult, errorCode, errorMessage, null)); } - public void startVideoRecording( - @NonNull Result result, @Nullable EventChannel imageStreamChannel) { - prepareRecording(result); + public void startVideoRecording(@Nullable EventChannel imageStreamChannel) { + prepareRecording(); if (imageStreamChannel != null) { setStreamHandler(imageStreamChannel); @@ -836,11 +818,10 @@ public void startVideoRecording( recordingVideo = true; try { startCapture(true, imageStreamChannel != null); - result.success(null); } catch (CameraAccessException e) { recordingVideo = false; captureFile = null; - result.error("videoRecordingFailed", e.getMessage(), null); + throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null); } } @@ -851,10 +832,9 @@ private void closeRenderer() { } } - public void stopVideoRecording(@NonNull final Result result) { + public String stopVideoRecording() { if (!recordingVideo) { - result.success(null); - return; + return ""; } // Re-create autofocus feature so it's using continuous capture focus mode now. cameraFeatures.setAutoFocus( @@ -871,16 +851,15 @@ public void stopVideoRecording(@NonNull final Result result) { try { startPreview(); } catch (CameraAccessException | IllegalStateException | InterruptedException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; + throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null); } - result.success(captureFile.getAbsolutePath()); + String path = captureFile.getAbsolutePath(); captureFile = null; + return path; } - public void pauseVideoRecording(@NonNull final Result result) { + public void pauseVideoRecording() { if (!recordingVideo) { - result.success(null); return; } @@ -888,20 +867,16 @@ public void pauseVideoRecording(@NonNull final Result result) { if (SdkCapabilityChecker.supportsVideoPause()) { mediaRecorder.pause(); } else { - result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); - return; + throw new Messages.FlutterError( + "videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); } } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; + throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null); } - - result.success(null); } - public void resumeVideoRecording(@NonNull final Result result) { + public void resumeVideoRecording() { if (!recordingVideo) { - result.success(null); return; } @@ -909,16 +884,12 @@ public void resumeVideoRecording(@NonNull final Result result) { if (SdkCapabilityChecker.supportsVideoPause()) { mediaRecorder.resume(); } else { - result.error( + throw new Messages.FlutterError( "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); - return; } } catch (IllegalStateException e) { - result.error("videoRecordingFailed", e.getMessage(), null); - return; + throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null); } - - result.success(null); } /** @@ -927,15 +898,18 @@ public void resumeVideoRecording(@NonNull final Result result) { * @param result Flutter result. * @param newMode new mode. */ - public void setFlashMode(@NonNull final Result result, @NonNull FlashMode newMode) { + public void setFlashMode(@NonNull final Messages.VoidResult result, @NonNull FlashMode newMode) { // Save the new flash mode setting. final FlashFeature flashFeature = cameraFeatures.getFlash(); flashFeature.setValue(newMode); flashFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( - () -> result.success(null), - (code, message) -> result.error("setFlashModeFailed", "Could not set flash mode.", null)); + result::success, + (code, message) -> + result.error( + new Messages.FlutterError( + "setFlashModeFailed", "Could not set flash mode.", null))); } /** @@ -944,15 +918,18 @@ public void setFlashMode(@NonNull final Result result, @NonNull FlashMode newMod * @param result Flutter result. * @param newMode new mode. */ - public void setExposureMode(@NonNull final Result result, @NonNull ExposureMode newMode) { + public void setExposureMode( + @NonNull final Messages.VoidResult result, @NonNull ExposureMode newMode) { final ExposureLockFeature exposureLockFeature = cameraFeatures.getExposureLock(); exposureLockFeature.setValue(newMode); exposureLockFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( - () -> result.success(null), + result::success, (code, message) -> - result.error("setExposureModeFailed", "Could not set exposure mode.", null)); + result.error( + new Messages.FlutterError( + "setExposureModeFailed", "Could not set exposure mode.", null))); } /** @@ -961,15 +938,17 @@ public void setExposureMode(@NonNull final Result result, @NonNull ExposureMode * @param result Flutter result. * @param point The exposure point. */ - public void setExposurePoint(@NonNull final Result result, @Nullable Point point) { + public void setExposurePoint(@NonNull final Messages.VoidResult result, @Nullable Point point) { final ExposurePointFeature exposurePointFeature = cameraFeatures.getExposurePoint(); exposurePointFeature.setValue(point); exposurePointFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( - () -> result.success(null), + result::success, (code, message) -> - result.error("setExposurePointFailed", "Could not set exposure point.", null)); + result.error( + new Messages.FlutterError( + "setExposurePointFailed", "Could not set exposure point.", null))); } /** Return the max exposure offset value supported by the camera to dart. */ @@ -990,10 +969,9 @@ public double getExposureOffsetStepSize() { /** * Sets new focus mode from dart. * - * @param result Flutter result. * @param newMode New mode. */ - public void setFocusMode(final Result result, @NonNull FocusMode newMode) { + public void setFocusMode(@NonNull FocusMode newMode) { final AutoFocusFeature autoFocusFeature = cameraFeatures.getAutoFocus(); autoFocusFeature.setValue(newMode); autoFocusFeature.updateBuilder(previewRequestBuilder); @@ -1020,11 +998,8 @@ public void setFocusMode(final Result result, @NonNull FocusMode newMode) { captureSession.setRepeatingRequest( previewRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { - if (result != null) { - result.error( - "setFocusModeFailed", "Error setting focus mode: " + e.getMessage(), null); - } - return; + throw new Messages.FlutterError( + "setFocusModeFailed", "Error setting focus mode: " + e.getMessage(), null); } break; case auto: @@ -1033,10 +1008,6 @@ public void setFocusMode(final Result result, @NonNull FocusMode newMode) { break; } } - - if (result != null) { - result.success(null); - } } /** @@ -1045,16 +1016,19 @@ public void setFocusMode(final Result result, @NonNull FocusMode newMode) { * @param result Flutter result. * @param point the new coordinates. */ - public void setFocusPoint(@NonNull final Result result, @Nullable Point point) { + public void setFocusPoint(@NonNull final Messages.VoidResult result, @Nullable Point point) { final FocusPointFeature focusPointFeature = cameraFeatures.getFocusPoint(); focusPointFeature.setValue(point); focusPointFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( - () -> result.success(null), - (code, message) -> result.error("setFocusPointFailed", "Could not set focus point.", null)); + result::success, + (code, message) -> + result.error( + new Messages.FlutterError( + "setFocusPointFailed", "Could not set focus point.", null))); - this.setFocusMode(null, cameraFeatures.getAutoFocus().getValue()); + this.setFocusMode(cameraFeatures.getAutoFocus().getValue()); } /** @@ -1064,7 +1038,7 @@ public void setFocusPoint(@NonNull final Result result, @Nullable Point point) { * @param result flutter result. * @param offset new value. */ - public void setExposureOffset(@NonNull final Result result, double offset) { + public void setExposureOffset(@NonNull final Messages.Result result, double offset) { final ExposureOffsetFeature exposureOffsetFeature = cameraFeatures.getExposureOffset(); exposureOffsetFeature.setValue(offset); exposureOffsetFeature.updateBuilder(previewRequestBuilder); @@ -1072,7 +1046,9 @@ public void setExposureOffset(@NonNull final Result result, double offset) { refreshPreviewCaptureSession( () -> result.success(exposureOffsetFeature.getValue()), (code, message) -> - result.error("setExposureOffsetFailed", "Could not set exposure offset.", null)); + result.error( + new Messages.FlutterError( + "setExposureOffsetFailed", "Could not set exposure offset.", null))); } public float getMaxZoomLevel() { @@ -1103,7 +1079,7 @@ DeviceOrientationManager getDeviceOrientationManager() { * @param result Flutter result. * @param zoom new value. */ - public void setZoomLevel(@NonNull final Result result, float zoom) throws CameraAccessException { + public void setZoomLevel(@NonNull final Messages.VoidResult result, float zoom) { final ZoomLevelFeature zoomLevel = cameraFeatures.getZoomLevel(); float maxZoom = zoomLevel.getMaximumZoomLevel(); float minZoom = zoomLevel.getMinimumZoomLevel(); @@ -1115,7 +1091,7 @@ public void setZoomLevel(@NonNull final Result result, float zoom) throws Camera "Zoom level out of bounds (zoom level should be between %f and %f).", minZoom, maxZoom); - result.error("ZOOM_ERROR", errorMessage, null); + result.error(new Messages.FlutterError("ZOOM_ERROR", errorMessage, null)); return; } @@ -1123,8 +1099,11 @@ public void setZoomLevel(@NonNull final Result result, float zoom) throws Camera zoomLevel.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( - () -> result.success(null), - (code, message) -> result.error("setZoomLevelFailed", "Could not set zoom level.", null)); + result::success, + (code, message) -> + result.error( + new Messages.FlutterError( + "setZoomLevelFailed", "Could not set zoom level.", null))); } /** @@ -1231,12 +1210,12 @@ public void onImageAvailable(ImageReader reader) { captureFile, new ImageSaver.Callback() { @Override - public void onComplete(String absolutePath) { + public void onComplete(@NonNull String absolutePath) { dartMessenger.finish(flutterResult, absolutePath); } @Override - public void onError(String errorCode, String errorMessage) { + public void onError(@NonNull String errorCode, @NonNull String errorMessage) { dartMessenger.error(flutterResult, errorCode, errorMessage, null); } })); @@ -1244,21 +1223,19 @@ public void onError(String errorCode, String errorMessage) { } @VisibleForTesting - void prepareRecording(@NonNull Result result) { + void prepareRecording() { final File outputDir = applicationContext.getCacheDir(); try { captureFile = File.createTempFile("REC", ".mp4", outputDir); } catch (IOException | SecurityException e) { - result.error("cannotCreateFile", e.getMessage(), null); - return; + throw new Messages.FlutterError("cannotCreateFile", e.getMessage(), null); } try { prepareMediaRecorder(captureFile.getAbsolutePath()); } catch (IOException e) { recordingVideo = false; captureFile = null; - result.error("videoRecordingFailed", e.getMessage(), null); - return; + throw new Messages.FlutterError("videoRecordingFailed", e.getMessage(), null); } // Re-create autofocus feature so it's using video focus mode now. cameraFeatures.setAutoFocus( @@ -1359,21 +1336,19 @@ public void uncaughtException(Thread thread, Throwable ex) { videoRendererUncaughtExceptionHandler); } - public void setDescriptionWhileRecording( - @NonNull final Result result, CameraProperties properties) { + public void setDescriptionWhileRecording(CameraProperties properties) { if (!recordingVideo) { - result.error("setDescriptionWhileRecordingFailed", "Device was not recording", null); - return; + throw new Messages.FlutterError( + "setDescriptionWhileRecordingFailed", "Device was not recording", null); } // See VideoRenderer.java; support for this EGL extension is required to switch camera while recording. if (!SdkCapabilityChecker.supportsEglRecordableAndroid()) { - result.error( + throw new Messages.FlutterError( "setDescriptionWhileRecordingFailed", "Device does not support switching the camera while recording", null); - return; } stopAndReleaseCamera(); @@ -1391,9 +1366,8 @@ public void setDescriptionWhileRecording( try { open(imageFormatGroup); } catch (CameraAccessException e) { - result.error("setDescriptionWhileRecordingFailed", e.getMessage(), null); + throw new Messages.FlutterError("setDescriptionWhileRecordingFailed", e.getMessage(), null); } - result.success(null); } public void dispose() { diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java new file mode 100644 index 00000000000..552a7ddfa3f --- /dev/null +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraApiImpl.java @@ -0,0 +1,352 @@ +// Copyright 2013 The Flutter 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; +import android.hardware.camera2.CameraAccessException; +import android.os.Handler; +import android.os.Looper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; +import io.flutter.plugins.camera.features.CameraFeatureFactoryImpl; +import io.flutter.plugins.camera.features.Point; +import io.flutter.plugins.camera.features.exposurelock.ExposureMode; +import io.flutter.plugins.camera.features.flash.FlashMode; +import io.flutter.plugins.camera.features.resolution.ResolutionPreset; +import io.flutter.view.TextureRegistry; +import java.util.Collections; +import java.util.List; + +final class CameraApiImpl implements Messages.CameraApi { + private final Activity activity; + private final BinaryMessenger messenger; + private final CameraPermissions cameraPermissions; + private final PermissionsRegistry permissionsRegistry; + private final TextureRegistry textureRegistry; + private final EventChannel imageStreamChannel; + @VisibleForTesting @Nullable Camera camera; + + CameraApiImpl( + Activity activity, + BinaryMessenger messenger, + CameraPermissions cameraPermissions, + PermissionsRegistry permissionsAdder, + TextureRegistry textureRegistry) { + this.activity = activity; + this.messenger = messenger; + this.cameraPermissions = cameraPermissions; + this.permissionsRegistry = permissionsAdder; + this.textureRegistry = textureRegistry; + + imageStreamChannel = + new EventChannel(messenger, "plugins.flutter.io/camera_android/imageStream"); + Messages.CameraApi.setUp(messenger, this); + } + + void tearDownMessageHandler() { + Messages.CameraApi.setUp(messenger, null); + } + + private Long instantiateCamera(String cameraName, Messages.PlatformMediaSettings settings) + throws CameraAccessException { + TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = + textureRegistry.createSurfaceTexture(); + long cameraId = flutterSurfaceTexture.id(); + DartMessenger dartMessenger = + new DartMessenger( + new Handler(Looper.getMainLooper()), + new Messages.CameraGlobalEventApi(messenger), + new Messages.CameraEventApi(messenger, String.valueOf(cameraId))); + CameraProperties cameraProperties = + new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity)); + Integer fps = (settings.getFps() == null) ? null : settings.getFps().intValue(); + Integer videoBitrate = + (settings.getVideoBitrate() == null) ? null : settings.getVideoBitrate().intValue(); + Integer audioBitrate = + (settings.getAudioBitrate() == null) ? null : settings.getAudioBitrate().intValue(); + ResolutionPreset resolutionPreset = + CameraUtils.resolutionPresetFromPigeon(settings.getResolutionPreset()); + + camera = + new Camera( + activity, + flutterSurfaceTexture, + new CameraFeatureFactoryImpl(), + dartMessenger, + cameraProperties, + new Camera.VideoCaptureSettings( + resolutionPreset, settings.getEnableAudio(), fps, videoBitrate, audioBitrate)); + + return flutterSurfaceTexture.id(); + } + + // We move catching CameraAccessException out of onMethodCall because it causes a crash + // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to + // to be able to compile with <21 sdks for apps that want the camera and support earlier version. + @SuppressWarnings("ConstantConditions") + private void handleException(Exception exception, Messages.Result result) { + // The code below exactly preserves the format of the native exceptions generated by pre-Pigeon + // code. Since `camera` currently leaks the raw platform exceptions to clients, there may be client + // code that relies on specific string values here, so these should not be changed. See + // https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#platform-exception-handling + // for longer-term solutions to this. + if (exception instanceof CameraAccessException) { + result.error(new Messages.FlutterError("CameraAccess", exception.getMessage(), null)); + return; + } + result.error(new Messages.FlutterError("error", exception.getMessage(), null)); + } + + private void handleException(Exception exception, Messages.VoidResult result) { + // The code below exactly preserves the format of the native exceptions generated by pre-Pigeon + // code. Since `camera` currently leaks the raw platform exceptions to clients, there may be client + // code that relies on specific string values here, so these should not be changed. See + // https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#platform-exception-handling + // for longer-term solutions to this. + if (exception instanceof CameraAccessException) { + result.error(new Messages.FlutterError("CameraAccess", exception.getMessage(), null)); + return; + } + result.error(new Messages.FlutterError("error", exception.getMessage(), null)); + } + + @NonNull + @Override + public List getAvailableCameras() { + if (activity == null) { + return Collections.emptyList(); + } + try { + return CameraUtils.getAvailableCameras(activity); + } catch (CameraAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void create( + @NonNull String cameraName, + @NonNull Messages.PlatformMediaSettings settings, + @NonNull Messages.Result result) { + if (camera != null) { + camera.close(); + } + + cameraPermissions.requestPermissions( + activity, + permissionsRegistry, + settings.getEnableAudio(), + (String errCode, String errDesc) -> { + if (errCode == null) { + try { + result.success(instantiateCamera(cameraName, settings)); + } catch (Exception e) { + handleException(e, result); + } + } else { + result.error(new Messages.FlutterError(errCode, errDesc, null)); + } + }); + } + + @Override + public void initialize(@NonNull Messages.PlatformImageFormatGroup imageFormat) { + if (camera == null) { + throw new Messages.FlutterError( + "cameraNotFound", + "Camera not found. Please call the 'create' method before calling 'initialize'.", + null); + } + try { + camera.open(CameraUtils.imageFormatGroupFromPigeon(imageFormat)); + } catch (CameraAccessException e) { + throw new Messages.FlutterError("CameraAccessException", e.getMessage(), null); + } + } + + @Override + public void takePicture(@NonNull Messages.Result result) { + camera.takePicture(result); + } + + @Override + public void startVideoRecording(@NonNull Boolean enableStream) { + camera.startVideoRecording(enableStream ? imageStreamChannel : null); + } + + @NonNull + @Override + public String stopVideoRecording() { + return camera.stopVideoRecording(); + } + + @Override + public void pauseVideoRecording() { + camera.pauseVideoRecording(); + } + + @Override + public void resumeVideoRecording() { + camera.resumeVideoRecording(); + } + + @Override + public void setFlashMode( + @NonNull Messages.PlatformFlashMode flashMode, @NonNull Messages.VoidResult result) { + FlashMode mode = CameraUtils.flashModeFromPigeon(flashMode); + try { + camera.setFlashMode(result, mode); + } catch (Exception e) { + handleException(e, result); + } + } + + @Override + public void setExposureMode( + @NonNull Messages.PlatformExposureMode exposureMode, @NonNull Messages.VoidResult result) { + ExposureMode mode = CameraUtils.exposureModeFromPigeon(exposureMode); + try { + camera.setExposureMode(result, mode); + } catch (Exception e) { + handleException(e, result); + } + } + + @Override + public void setExposurePoint( + @Nullable Messages.PlatformPoint point, @NonNull Messages.VoidResult result) { + try { + camera.setExposurePoint(result, point == null ? null : new Point(point.getX(), point.getY())); + } catch (Exception e) { + handleException(e, result); + } + } + + @NonNull + @Override + public Double getMinExposureOffset() { + return camera.getMinExposureOffset(); + } + + @NonNull + @Override + public Double getMaxExposureOffset() { + return camera.getMaxExposureOffset(); + } + + @NonNull + @Override + public Double getExposureOffsetStepSize() { + return camera.getExposureOffsetStepSize(); + } + + @Override + public void setExposureOffset(@NonNull Double offset, @NonNull Messages.Result result) { + try { + camera.setExposureOffset(result, offset); + } catch (Exception e) { + handleException(e, result); + } + } + + @Override + public void setFocusMode(@NonNull Messages.PlatformFocusMode focusMode) { + camera.setFocusMode(CameraUtils.focusModeFromPigeon(focusMode)); + } + + @Override + public void setFocusPoint( + @Nullable Messages.PlatformPoint point, @NonNull Messages.VoidResult result) { + try { + camera.setFocusPoint(result, point == null ? null : new Point(point.getX(), point.getY())); + } catch (Exception e) { + handleException(e, result); + } + } + + @Override + public void startImageStream() { + try { + camera.startPreviewWithImageStream(imageStreamChannel); + } catch (CameraAccessException e) { + throw new Messages.FlutterError("CameraAccessException", e.getMessage(), null); + } + } + + @Override + public void stopImageStream() { + try { + camera.startPreview(); + } catch (Exception e) { + throw new Messages.FlutterError(e.getClass().getName(), e.getMessage(), null); + } + } + + @NonNull + @Override + public Double getMaxZoomLevel() { + assert camera != null; + return (double) camera.getMaxZoomLevel(); + } + + @NonNull + @Override + public Double getMinZoomLevel() { + assert camera != null; + return (double) camera.getMinZoomLevel(); + } + + @Override + public void setZoomLevel(@NonNull Double zoom, @NonNull Messages.VoidResult result) { + assert camera != null; + camera.setZoomLevel(result, zoom.floatValue()); + } + + @Override + public void lockCaptureOrientation( + @NonNull Messages.PlatformDeviceOrientation platformOrientation) { + camera.lockCaptureOrientation(CameraUtils.orientationFromPigeon(platformOrientation)); + } + + @Override + public void unlockCaptureOrientation() { + camera.unlockCaptureOrientation(); + } + + @Override + public void pausePreview() { + try { + camera.pausePreview(); + } catch (CameraAccessException e) { + throw new Messages.FlutterError("CameraAccessException", e.getMessage(), null); + } + } + + @Override + public void resumePreview() { + camera.resumePreview(); + } + + @Override + public void setDescriptionWhileRecording(@NonNull String cameraName) { + try { + camera.setDescriptionWhileRecording( + new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity))); + } catch (CameraAccessException e) { + throw new Messages.FlutterError("CameraAccessException", e.getMessage(), null); + } + } + + @Override + public void dispose() { + if (camera != null) { + camera.dispose(); + } + } +} diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java index aff225e2c35..e7f6a56a976 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java @@ -24,7 +24,7 @@ public final class CameraPlugin implements FlutterPlugin, ActivityAware { private static final String TAG = "CameraPlugin"; private @Nullable FlutterPluginBinding flutterPluginBinding; - private @Nullable MethodCallHandlerImpl methodCallHandler; + private @Nullable CameraApiImpl cameraApi; /** * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment. @@ -55,9 +55,9 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { @Override public void onDetachedFromActivity() { // Could be on too low of an SDK to have started listening originally. - if (methodCallHandler != null) { - methodCallHandler.stopListening(); - methodCallHandler = null; + if (cameraApi != null) { + cameraApi.tearDownMessageHandler(); + cameraApi = null; } } @@ -76,8 +76,8 @@ private void maybeStartListening( BinaryMessenger messenger, PermissionsRegistry permissionsRegistry, TextureRegistry textureRegistry) { - methodCallHandler = - new MethodCallHandlerImpl( + cameraApi = + new CameraApiImpl( activity, messenger, new CameraPermissions(), permissionsRegistry, textureRegistry); } } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java index edd873d82b9..c5f077c9e18 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java @@ -6,6 +6,7 @@ import android.app.Activity; import android.content.Context; +import android.graphics.ImageFormat; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; @@ -14,6 +15,8 @@ import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugins.camera.features.autofocus.FocusMode; import io.flutter.plugins.camera.features.exposurelock.ExposureMode; +import io.flutter.plugins.camera.features.flash.FlashMode; +import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import java.util.ArrayList; import java.util.List; @@ -32,59 +35,6 @@ static CameraManager getCameraManager(Context context) { return (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); } - /** - * Serializes the {@link PlatformChannel.DeviceOrientation} to a string value. - * - * @param orientation The orientation to serialize. - * @return The serialized orientation. - * @throws UnsupportedOperationException when the provided orientation not have a corresponding - * string value. - */ - static String serializeDeviceOrientation(PlatformChannel.DeviceOrientation orientation) { - if (orientation == null) - throw new UnsupportedOperationException("Could not serialize null device orientation."); - switch (orientation) { - case PORTRAIT_UP: - return "portraitUp"; - case PORTRAIT_DOWN: - return "portraitDown"; - case LANDSCAPE_LEFT: - return "landscapeLeft"; - case LANDSCAPE_RIGHT: - return "landscapeRight"; - default: - throw new UnsupportedOperationException( - "Could not serialize device orientation: " + orientation.toString()); - } - } - - /** - * Deserializes a string value to its corresponding {@link PlatformChannel.DeviceOrientation} - * value. - * - * @param orientation The string value to deserialize. - * @return The deserialized orientation. - * @throws UnsupportedOperationException when the provided string value does not have a - * corresponding {@link PlatformChannel.DeviceOrientation}. - */ - static PlatformChannel.DeviceOrientation deserializeDeviceOrientation(String orientation) { - if (orientation == null) - throw new UnsupportedOperationException("Could not deserialize null device orientation."); - switch (orientation) { - case "portraitUp": - return PlatformChannel.DeviceOrientation.PORTRAIT_UP; - case "portraitDown": - return PlatformChannel.DeviceOrientation.PORTRAIT_DOWN; - case "landscapeLeft": - return PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT; - case "landscapeRight": - return PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT; - default: - throw new UnsupportedOperationException( - "Could not deserialize device orientation: " + orientation); - } - } - /** * Converts a raw integer to a PlatformCameraLensDirection enum. * @@ -169,6 +119,28 @@ public static Messages.PlatformDeviceOrientation orientationToPigeon( return Messages.PlatformDeviceOrientation.PORTRAIT_UP; } + /** + * Converts a PlatformDeviceOrientation from Pigeon to DeviceOrientation from PlatformChannel. + * + * @param orientation A PlatformDeviceOrientation + * @return The corresponding DeviceOrientation. Defaults to PORTRAIT_UP. + */ + @NonNull + public static PlatformChannel.DeviceOrientation orientationFromPigeon( + @NonNull Messages.PlatformDeviceOrientation orientation) { + switch (orientation) { + case PORTRAIT_UP: + return PlatformChannel.DeviceOrientation.PORTRAIT_UP; + case PORTRAIT_DOWN: + return PlatformChannel.DeviceOrientation.PORTRAIT_DOWN; + case LANDSCAPE_LEFT: + return PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT; + case LANDSCAPE_RIGHT: + return PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT; + } + throw new IllegalStateException("Unreachable code"); + } + /** * Converts a FocusMode from the autofocus package to a PlatformFocusMode from Pigeon. * @@ -186,6 +158,23 @@ public static Messages.PlatformFocusMode focusModeToPigeon(@NonNull FocusMode fo return Messages.PlatformFocusMode.AUTO; } + /** + * Converts a PlatformFocusMode from Pigeon to a FocusMode from the autofocus package. + * + * @param focusMode A PlatformFocusMode. + * @return The corresponding FocusMode. + */ + @NonNull + public static FocusMode focusModeFromPigeon(@NonNull Messages.PlatformFocusMode focusMode) { + switch (focusMode) { + case AUTO: + return FocusMode.auto; + case LOCKED: + return FocusMode.locked; + } + throw new IllegalStateException("Unreachable code"); + } + /** * Converts an ExposureMode from the exposurelock package to a PlatformExposureMode from Pigeon. * @@ -203,4 +192,89 @@ public static Messages.PlatformExposureMode exposureModeToPigeon( } return Messages.PlatformExposureMode.AUTO; } + + /** + * Converts a PlatformExposureMode to ExposureMode from the exposurelock package. + * + * @param mode A PlatformExposureMode. + * @return The corresponding ExposureMode. + */ + @NonNull + public static ExposureMode exposureModeFromPigeon(@NonNull Messages.PlatformExposureMode mode) { + switch (mode) { + case AUTO: + return ExposureMode.auto; + case LOCKED: + return ExposureMode.locked; + } + throw new IllegalStateException("Unreachable code"); + } + + /** + * Converts a PlatformResolutionPreset from Pigeon to a ResolutionPreset from the resolution + * package. + * + * @param preset A PlatformResolutionPreset. + * @return The corresponding ResolutionPreset. + */ + @NonNull + public static ResolutionPreset resolutionPresetFromPigeon( + @NonNull Messages.PlatformResolutionPreset preset) { + switch (preset) { + case LOW: + return ResolutionPreset.low; + case MEDIUM: + return ResolutionPreset.medium; + case HIGH: + return ResolutionPreset.high; + case VERY_HIGH: + return ResolutionPreset.veryHigh; + case ULTRA_HIGH: + return ResolutionPreset.ultraHigh; + case MAX: + return ResolutionPreset.max; + } + throw new IllegalStateException("Unreachable code"); + } + + /** + * Converts a PlatformImageFormatGroup from Pigeon to an Integer representing an image format. + * + * @param format A PlatformImageFormatGroup. + * @return The corresponding integer code. Defaults to YUV_420_888. + */ + @NonNull + public static Integer imageFormatGroupFromPigeon( + @NonNull Messages.PlatformImageFormatGroup format) { + switch (format) { + case YUV420: + return ImageFormat.YUV_420_888; + case JPEG: + return ImageFormat.JPEG; + case NV21: + return ImageFormat.NV21; + } + throw new IllegalStateException("Unreachable code"); + } + + /** + * Converts a PlatformFlashMode from Pigeon to a FlashMode from the flash package. + * + * @param mode A PlatformFlashMode. + * @return The corresponding FlashMode. + */ + @NonNull + public static FlashMode flashModeFromPigeon(@NonNull Messages.PlatformFlashMode mode) { + switch (mode) { + case AUTO: + return FlashMode.auto; + case OFF: + return FlashMode.off; + case ALWAYS: + return FlashMode.always; + case TORCH: + return FlashMode.torch; + } + throw new IllegalStateException("Unreachable code"); + } } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 819c57da043..3dcb1d91a46 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -113,7 +113,7 @@ void sendCameraErrorEvent(@Nullable String description) { * * @param payload The payload to send. */ - public void finish(@NonNull MethodChannel.Result result, @Nullable Object payload) { + public void finish(@NonNull Messages.Result result, @NonNull T payload) { handler.post(() -> result.success(payload)); } @@ -124,11 +124,12 @@ public void finish(@NonNull MethodChannel.Result result, @Nullable Object payloa * @param errorMessage error message. * @param errorDetails error details. */ - public void error( - @NonNull MethodChannel.Result result, + public void error( + @NonNull Messages.Result result, @NonNull String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { - handler.post(() -> result.error(errorCode, errorMessage, errorDetails)); + handler.post( + () -> result.error(new Messages.FlutterError(errorCode, errorMessage, errorDetails))); } } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java index ef9fefe8e86..bdb6e2b4138 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java @@ -123,6 +123,50 @@ public enum PlatformFocusMode { } } + /** Pigeon equivalent of [ResolutionPreset]. */ + public enum PlatformResolutionPreset { + LOW(0), + MEDIUM(1), + HIGH(2), + VERY_HIGH(3), + ULTRA_HIGH(4), + MAX(5); + + final int index; + + PlatformResolutionPreset(final int index) { + this.index = index; + } + } + + /** Pigeon equivalent of [ImageFormatGroup]. */ + public enum PlatformImageFormatGroup { + /** The default for Android. */ + YUV420(0), + JPEG(1), + NV21(2); + + final int index; + + PlatformImageFormatGroup(final int index) { + this.index = index; + } + } + + /** Pigeon equivalent of [FlashMode]. */ + public enum PlatformFlashMode { + OFF(0), + AUTO(1), + ALWAYS(2), + TORCH(3); + + final int index; + + PlatformFlashMode(final int index) { + this.index = index; + } + } + /** * Pigeon equivalent of [CameraDescription]. * @@ -518,6 +562,268 @@ ArrayList toList() { } } + /** + * Pigeon equivalent of [Point]. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPoint { + private @NonNull Double x; + + public @NonNull Double getX() { + return x; + } + + public void setX(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"x\" is null."); + } + this.x = setterArg; + } + + private @NonNull Double y; + + public @NonNull Double getY() { + return y; + } + + public void setY(@NonNull Double setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"y\" is null."); + } + this.y = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPoint() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformPoint that = (PlatformPoint) o; + return x.equals(that.x) && y.equals(that.y); + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + public static final class Builder { + + private @Nullable Double x; + + @CanIgnoreReturnValue + public @NonNull Builder setX(@NonNull Double setterArg) { + this.x = setterArg; + return this; + } + + private @Nullable Double y; + + @CanIgnoreReturnValue + public @NonNull Builder setY(@NonNull Double setterArg) { + this.y = setterArg; + return this; + } + + public @NonNull PlatformPoint build() { + PlatformPoint pigeonReturn = new PlatformPoint(); + pigeonReturn.setX(x); + pigeonReturn.setY(y); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(2); + toListResult.add(x); + toListResult.add(y); + return toListResult; + } + + static @NonNull PlatformPoint fromList(@NonNull ArrayList pigeonVar_list) { + PlatformPoint pigeonResult = new PlatformPoint(); + Object x = pigeonVar_list.get(0); + pigeonResult.setX((Double) x); + Object y = pigeonVar_list.get(1); + pigeonResult.setY((Double) y); + return pigeonResult; + } + } + + /** + * Pigeon equivalent of [MediaSettings]. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformMediaSettings { + private @NonNull PlatformResolutionPreset resolutionPreset; + + public @NonNull PlatformResolutionPreset getResolutionPreset() { + return resolutionPreset; + } + + public void setResolutionPreset(@NonNull PlatformResolutionPreset setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"resolutionPreset\" is null."); + } + this.resolutionPreset = setterArg; + } + + private @Nullable Long fps; + + public @Nullable Long getFps() { + return fps; + } + + public void setFps(@Nullable Long setterArg) { + this.fps = setterArg; + } + + private @Nullable Long videoBitrate; + + public @Nullable Long getVideoBitrate() { + return videoBitrate; + } + + public void setVideoBitrate(@Nullable Long setterArg) { + this.videoBitrate = setterArg; + } + + private @Nullable Long audioBitrate; + + public @Nullable Long getAudioBitrate() { + return audioBitrate; + } + + public void setAudioBitrate(@Nullable Long setterArg) { + this.audioBitrate = setterArg; + } + + private @NonNull Boolean enableAudio; + + public @NonNull Boolean getEnableAudio() { + return enableAudio; + } + + public void setEnableAudio(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"enableAudio\" is null."); + } + this.enableAudio = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformMediaSettings() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformMediaSettings that = (PlatformMediaSettings) o; + return resolutionPreset.equals(that.resolutionPreset) + && Objects.equals(fps, that.fps) + && Objects.equals(videoBitrate, that.videoBitrate) + && Objects.equals(audioBitrate, that.audioBitrate) + && enableAudio.equals(that.enableAudio); + } + + @Override + public int hashCode() { + return Objects.hash(resolutionPreset, fps, videoBitrate, audioBitrate, enableAudio); + } + + public static final class Builder { + + private @Nullable PlatformResolutionPreset resolutionPreset; + + @CanIgnoreReturnValue + public @NonNull Builder setResolutionPreset(@NonNull PlatformResolutionPreset setterArg) { + this.resolutionPreset = setterArg; + return this; + } + + private @Nullable Long fps; + + @CanIgnoreReturnValue + public @NonNull Builder setFps(@Nullable Long setterArg) { + this.fps = setterArg; + return this; + } + + private @Nullable Long videoBitrate; + + @CanIgnoreReturnValue + public @NonNull Builder setVideoBitrate(@Nullable Long setterArg) { + this.videoBitrate = setterArg; + return this; + } + + private @Nullable Long audioBitrate; + + @CanIgnoreReturnValue + public @NonNull Builder setAudioBitrate(@Nullable Long setterArg) { + this.audioBitrate = setterArg; + return this; + } + + private @Nullable Boolean enableAudio; + + @CanIgnoreReturnValue + public @NonNull Builder setEnableAudio(@NonNull Boolean setterArg) { + this.enableAudio = setterArg; + return this; + } + + public @NonNull PlatformMediaSettings build() { + PlatformMediaSettings pigeonReturn = new PlatformMediaSettings(); + pigeonReturn.setResolutionPreset(resolutionPreset); + pigeonReturn.setFps(fps); + pigeonReturn.setVideoBitrate(videoBitrate); + pigeonReturn.setAudioBitrate(audioBitrate); + pigeonReturn.setEnableAudio(enableAudio); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(5); + toListResult.add(resolutionPreset); + toListResult.add(fps); + toListResult.add(videoBitrate); + toListResult.add(audioBitrate); + toListResult.add(enableAudio); + return toListResult; + } + + static @NonNull PlatformMediaSettings fromList(@NonNull ArrayList pigeonVar_list) { + PlatformMediaSettings pigeonResult = new PlatformMediaSettings(); + Object resolutionPreset = pigeonVar_list.get(0); + pigeonResult.setResolutionPreset((PlatformResolutionPreset) resolutionPreset); + Object fps = pigeonVar_list.get(1); + pigeonResult.setFps((Long) fps); + Object videoBitrate = pigeonVar_list.get(2); + pigeonResult.setVideoBitrate((Long) videoBitrate); + Object audioBitrate = pigeonVar_list.get(3); + pigeonResult.setAudioBitrate((Long) audioBitrate); + Object enableAudio = pigeonVar_list.get(4); + pigeonResult.setEnableAudio((Boolean) enableAudio); + return pigeonResult; + } + } + private static class PigeonCodec extends StandardMessageCodec { public static final PigeonCodec INSTANCE = new PigeonCodec(); @@ -551,11 +857,34 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { return value == null ? null : PlatformFocusMode.values()[((Long) value).intValue()]; } case (byte) 133: - return PlatformCameraDescription.fromList((ArrayList) readValue(buffer)); + { + Object value = readValue(buffer); + return value == null + ? null + : PlatformResolutionPreset.values()[((Long) value).intValue()]; + } case (byte) 134: - return PlatformCameraState.fromList((ArrayList) readValue(buffer)); + { + Object value = readValue(buffer); + return value == null + ? null + : PlatformImageFormatGroup.values()[((Long) value).intValue()]; + } case (byte) 135: + { + Object value = readValue(buffer); + return value == null ? null : PlatformFlashMode.values()[((Long) value).intValue()]; + } + case (byte) 136: + return PlatformCameraDescription.fromList((ArrayList) readValue(buffer)); + case (byte) 137: + return PlatformCameraState.fromList((ArrayList) readValue(buffer)); + case (byte) 138: return PlatformSize.fromList((ArrayList) readValue(buffer)); + case (byte) 139: + return PlatformPoint.fromList((ArrayList) readValue(buffer)); + case (byte) 140: + return PlatformMediaSettings.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -575,15 +904,30 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof PlatformFocusMode) { stream.write(132); writeValue(stream, value == null ? null : ((PlatformFocusMode) value).index); - } else if (value instanceof PlatformCameraDescription) { + } else if (value instanceof PlatformResolutionPreset) { stream.write(133); + writeValue(stream, value == null ? null : ((PlatformResolutionPreset) value).index); + } else if (value instanceof PlatformImageFormatGroup) { + stream.write(134); + writeValue(stream, value == null ? null : ((PlatformImageFormatGroup) value).index); + } else if (value instanceof PlatformFlashMode) { + stream.write(135); + writeValue(stream, value == null ? null : ((PlatformFlashMode) value).index); + } else if (value instanceof PlatformCameraDescription) { + stream.write(136); writeValue(stream, ((PlatformCameraDescription) value).toList()); } else if (value instanceof PlatformCameraState) { - stream.write(134); + stream.write(137); writeValue(stream, ((PlatformCameraState) value).toList()); } else if (value instanceof PlatformSize) { - stream.write(135); + stream.write(138); writeValue(stream, ((PlatformSize) value).toList()); + } else if (value instanceof PlatformPoint) { + stream.write(139); + writeValue(stream, ((PlatformPoint) value).toList()); + } else if (value instanceof PlatformMediaSettings) { + stream.write(140); + writeValue(stream, ((PlatformMediaSettings) value).toList()); } else { super.writeValue(stream, value); } @@ -623,6 +967,87 @@ public interface CameraApi { /** Returns the list of available cameras. */ @NonNull List getAvailableCameras(); + /** Creates a new camera with the given name and settings and returns its ID. */ + void create( + @NonNull String cameraName, + @NonNull PlatformMediaSettings mediaSettings, + @NonNull Result result); + /** Initializes the camera with the given ID for the given image format. */ + void initialize(@NonNull PlatformImageFormatGroup imageFormat); + /** Disposes of the camera with the given ID. */ + void dispose(); + /** Locks the camera with the given ID to the given orientation. */ + void lockCaptureOrientation(@NonNull PlatformDeviceOrientation orientation); + /** Unlocks the orientation for the camera with the given ID. */ + void unlockCaptureOrientation(); + /** Takes a picture on the camera with the given ID and returns a path to the resulting file. */ + void takePicture(@NonNull Result result); + /** Starts recording a video on the camera with the given ID. */ + void startVideoRecording(@NonNull Boolean enableStream); + /** + * Ends video recording on the camera with the given ID and returns the path to the resulting + * file. + */ + @NonNull + String stopVideoRecording(); + /** Pauses video recording on the camera with the given ID. */ + void pauseVideoRecording(); + /** Resumes previously paused video recording on the camera with the given ID. */ + void resumeVideoRecording(); + /** Begins streaming frames from the camera. */ + void startImageStream(); + /** Stops streaming frames from the camera. */ + void stopImageStream(); + /** Sets the flash mode of the camera with the given ID. */ + void setFlashMode(@NonNull PlatformFlashMode flashMode, @NonNull VoidResult result); + /** Sets the exposure mode of the camera with the given ID. */ + void setExposureMode(@NonNull PlatformExposureMode exposureMode, @NonNull VoidResult result); + /** + * Sets the exposure point of the camera with the given ID. + * + *

A null value resets to the default exposure point. + */ + void setExposurePoint(@Nullable PlatformPoint point, @NonNull VoidResult result); + /** Returns the minimum exposure offset of the camera with the given ID. */ + @NonNull + Double getMinExposureOffset(); + /** Returns the maximum exposure offset of the camera with the given ID. */ + @NonNull + Double getMaxExposureOffset(); + /** Returns the exposure step size of the camera with the given ID. */ + @NonNull + Double getExposureOffsetStepSize(); + /** + * Sets the exposure offset of the camera with the given ID and returns the actual exposure + * offset. + */ + void setExposureOffset(@NonNull Double offset, @NonNull Result result); + /** Sets the focus mode of the camera with the given ID. */ + void setFocusMode(@NonNull PlatformFocusMode focusMode); + /** + * Sets the focus point of the camera with the given ID. + * + *

A null value resets to the default focus point. + */ + void setFocusPoint(@Nullable PlatformPoint point, @NonNull VoidResult result); + /** Returns the maximum zoom level of the camera with the given ID. */ + @NonNull + Double getMaxZoomLevel(); + /** Returns the minimum zoom level of the camera with the given ID. */ + @NonNull + Double getMinZoomLevel(); + /** Sets the zoom level of the camera with the given ID. */ + void setZoomLevel(@NonNull Double zoom, @NonNull VoidResult result); + /** Pauses streaming of preview frames. */ + void pausePreview(); + /** Resumes previously paused streaming of preview frames. */ + void resumePreview(); + /** + * Changes the camera while recording video. + * + *

This should be called only while video recording is active. + */ + void setDescriptionWhileRecording(@NonNull String description); /** The codec used by CameraApi. */ static @NonNull MessageCodec getCodec() { @@ -661,6 +1086,698 @@ static void setUp( channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.create" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + String cameraNameArg = (String) args.get(0); + PlatformMediaSettings mediaSettingsArg = (PlatformMediaSettings) args.get(1); + Result resultCallback = + new Result() { + public void success(Long result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.create(cameraNameArg, mediaSettingsArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.initialize" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + PlatformImageFormatGroup imageFormatArg = (PlatformImageFormatGroup) args.get(0); + try { + api.initialize(imageFormatArg); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.dispose" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + api.dispose(); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.lockCaptureOrientation" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + PlatformDeviceOrientation orientationArg = (PlatformDeviceOrientation) args.get(0); + try { + api.lockCaptureOrientation(orientationArg); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.unlockCaptureOrientation" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + api.unlockCaptureOrientation(); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.takePicture" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + Result resultCallback = + new Result() { + public void success(String result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.takePicture(resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.startVideoRecording" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + Boolean enableStreamArg = (Boolean) args.get(0); + try { + api.startVideoRecording(enableStreamArg); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.stopVideoRecording" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + String output = api.stopVideoRecording(); + wrapped.add(0, output); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.pauseVideoRecording" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + api.pauseVideoRecording(); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.resumeVideoRecording" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + api.resumeVideoRecording(); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.startImageStream" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + api.startImageStream(); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.stopImageStream" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + api.stopImageStream(); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setFlashMode" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + PlatformFlashMode flashModeArg = (PlatformFlashMode) args.get(0); + VoidResult resultCallback = + new VoidResult() { + public void success() { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.setFlashMode(flashModeArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setExposureMode" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + PlatformExposureMode exposureModeArg = (PlatformExposureMode) args.get(0); + VoidResult resultCallback = + new VoidResult() { + public void success() { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.setExposureMode(exposureModeArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setExposurePoint" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + PlatformPoint pointArg = (PlatformPoint) args.get(0); + VoidResult resultCallback = + new VoidResult() { + public void success() { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.setExposurePoint(pointArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.getMinExposureOffset" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + Double output = api.getMinExposureOffset(); + wrapped.add(0, output); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.getMaxExposureOffset" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + Double output = api.getMaxExposureOffset(); + wrapped.add(0, output); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.getExposureOffsetStepSize" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + Double output = api.getExposureOffsetStepSize(); + wrapped.add(0, output); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setExposureOffset" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + Double offsetArg = (Double) args.get(0); + Result resultCallback = + new Result() { + public void success(Double result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.setExposureOffset(offsetArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setFocusMode" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + PlatformFocusMode focusModeArg = (PlatformFocusMode) args.get(0); + try { + api.setFocusMode(focusModeArg); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setFocusPoint" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + PlatformPoint pointArg = (PlatformPoint) args.get(0); + VoidResult resultCallback = + new VoidResult() { + public void success() { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.setFocusPoint(pointArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.getMaxZoomLevel" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + Double output = api.getMaxZoomLevel(); + wrapped.add(0, output); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.getMinZoomLevel" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + Double output = api.getMinZoomLevel(); + wrapped.add(0, output); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setZoomLevel" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + Double zoomArg = (Double) args.get(0); + VoidResult resultCallback = + new VoidResult() { + public void success() { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.setZoomLevel(zoomArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.pausePreview" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + api.pausePreview(); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.resumePreview" + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + api.resumePreview(); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.setDescriptionWhileRecording" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + String descriptionArg = (String) args.get(0); + try { + api.setDescriptionWhileRecording(descriptionArg); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } /** diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java deleted file mode 100644 index a02872bd490..00000000000 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright 2013 The Flutter 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; -import android.hardware.camera2.CameraAccessException; -import android.os.Handler; -import android.os.Looper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import io.flutter.embedding.engine.systemchannels.PlatformChannel; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodCall; -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.features.CameraFeatureFactoryImpl; -import io.flutter.plugins.camera.features.Point; -import io.flutter.plugins.camera.features.autofocus.FocusMode; -import io.flutter.plugins.camera.features.exposurelock.ExposureMode; -import io.flutter.plugins.camera.features.flash.FlashMode; -import io.flutter.plugins.camera.features.resolution.ResolutionPreset; -import io.flutter.view.TextureRegistry; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler, Messages.CameraApi { - private final Activity activity; - private final BinaryMessenger messenger; - private final CameraPermissions cameraPermissions; - private final PermissionsRegistry permissionsRegistry; - private final TextureRegistry textureRegistry; - private final MethodChannel methodChannel; - private final EventChannel imageStreamChannel; - @VisibleForTesting @Nullable Camera camera; - - MethodCallHandlerImpl( - Activity activity, - BinaryMessenger messenger, - CameraPermissions cameraPermissions, - PermissionsRegistry permissionsAdder, - TextureRegistry textureRegistry) { - this.activity = activity; - this.messenger = messenger; - this.cameraPermissions = cameraPermissions; - this.permissionsRegistry = permissionsAdder; - this.textureRegistry = textureRegistry; - - methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera_android"); - imageStreamChannel = - new EventChannel(messenger, "plugins.flutter.io/camera_android/imageStream"); - methodChannel.setMethodCallHandler(this); - Messages.CameraApi.setUp(messenger, this); - } - - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) { - switch (call.method) { - case "create": - { - if (camera != null) { - camera.close(); - } - - cameraPermissions.requestPermissions( - activity, - permissionsRegistry, - call.argument("enableAudio"), - (String errCode, String errDesc) -> { - if (errCode == null) { - try { - instantiateCamera(call, result); - } catch (Exception e) { - handleException(e, result); - } - } else { - result.error(errCode, errDesc, null); - } - }); - break; - } - case "initialize": - { - if (camera != null) { - try { - camera.open(call.argument("imageFormatGroup")); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - } else { - result.error( - "cameraNotFound", - "Camera not found. Please call the 'create' method before calling 'initialize'.", - null); - } - break; - } - case "takePicture": - { - camera.takePicture(result); - break; - } - case "prepareForVideoRecording": - { - // This optimization is not required for Android. - result.success(null); - break; - } - case "startVideoRecording": - { - camera.startVideoRecording( - result, - Objects.equals(call.argument("enableStream"), true) ? imageStreamChannel : null); - break; - } - case "stopVideoRecording": - { - camera.stopVideoRecording(result); - break; - } - case "pauseVideoRecording": - { - camera.pauseVideoRecording(result); - break; - } - case "resumeVideoRecording": - { - camera.resumeVideoRecording(result); - break; - } - case "setFlashMode": - { - String modeStr = call.argument("mode"); - FlashMode mode = FlashMode.getValueForString(modeStr); - if (mode == null) { - result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null); - return; - } - try { - camera.setFlashMode(result, mode); - } catch (Exception e) { - handleException(e, 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, new Point(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 "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, new Point(x, y)); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "startImageStream": - { - try { - camera.startPreviewWithImageStream(imageStreamChannel); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "stopImageStream": - { - try { - camera.startPreview(); - result.success(null); - } catch (Exception e) { - handleException(e, 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 "lockCaptureOrientation": - { - PlatformChannel.DeviceOrientation orientation = - CameraUtils.deserializeDeviceOrientation(call.argument("orientation")); - - try { - camera.lockCaptureOrientation(orientation); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "unlockCaptureOrientation": - { - try { - camera.unlockCaptureOrientation(); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "pausePreview": - { - try { - camera.pausePreview(); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "resumePreview": - { - camera.resumePreview(); - result.success(null); - break; - } - case "setDescriptionWhileRecording": - { - try { - String cameraName = call.argument("cameraName"); - CameraProperties cameraProperties = - new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity)); - camera.setDescriptionWhileRecording(result, cameraProperties); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "dispose": - { - if (camera != null) { - camera.dispose(); - } - result.success(null); - break; - } - default: - result.notImplemented(); - break; - } - } - - void stopListening() { - methodChannel.setMethodCallHandler(null); - } - - private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException { - String cameraName = call.argument("cameraName"); - String preset = call.argument("resolutionPreset"); - boolean enableAudio = call.argument("enableAudio"); - Integer fps = call.argument("fps"); - Integer videoBitrate = call.argument("videoBitrate"); - Integer audioBitrate = call.argument("audioBitrate"); - - TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = - textureRegistry.createSurfaceTexture(); - long cameraId = flutterSurfaceTexture.id(); - DartMessenger dartMessenger = - new DartMessenger( - new Handler(Looper.getMainLooper()), - new Messages.CameraGlobalEventApi(messenger), - new Messages.CameraEventApi(messenger, String.valueOf(cameraId))); - CameraProperties cameraProperties = - new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity)); - ResolutionPreset resolutionPreset = ResolutionPreset.valueOf(preset); - - camera = - new Camera( - activity, - flutterSurfaceTexture, - new CameraFeatureFactoryImpl(), - dartMessenger, - cameraProperties, - new Camera.VideoCaptureSettings( - resolutionPreset, enableAudio, fps, videoBitrate, audioBitrate)); - - Map reply = new HashMap<>(); - reply.put("cameraId", flutterSurfaceTexture.id()); - result.success(reply); - } - - // We move catching CameraAccessException out of onMethodCall because it causes a crash - // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to - // to be able to compile with <21 sdks for apps that want the camera and support earlier version. - @SuppressWarnings("ConstantConditions") - private void handleException(Exception exception, Result result) { - if (exception instanceof CameraAccessException) { - result.error("CameraAccess", exception.getMessage(), null); - return; - } - - // CameraAccessException can not be cast to a RuntimeException. - throw (RuntimeException) exception; - } - - @NonNull - @Override - public List getAvailableCameras() { - if (activity == null) { - return Collections.emptyList(); - } - try { - return CameraUtils.getAvailableCameras(activity); - } catch (CameraAccessException e) { - throw new RuntimeException(e); - } - } -} diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/MethodCallHandlerImplTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraApiImplTest.java similarity index 69% rename from packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/MethodCallHandlerImplTest.java rename to packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraApiImplTest.java index e4ab82d9eda..88430530142 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/MethodCallHandlerImplTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraApiImplTest.java @@ -5,6 +5,7 @@ package io.flutter.plugins.camera; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -14,35 +15,33 @@ import android.hardware.camera2.CameraAccessException; import androidx.lifecycle.LifecycleObserver; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; import io.flutter.view.TextureRegistry; import org.junit.Before; import org.junit.Test; -public class MethodCallHandlerImplTest { +public class CameraApiImplTest { - MethodCallHandlerImpl handler; - MethodChannel.Result mockResult; + CameraApiImpl handler; + Messages.VoidResult mockResult; Camera mockCamera; @Before public void setUp() { handler = - new MethodCallHandlerImpl( + new CameraApiImpl( mock(Activity.class), mock(BinaryMessenger.class), mock(CameraPermissions.class), mock(CameraPermissions.PermissionsRegistry.class), mock(TextureRegistry.class)); - mockResult = mock(MethodChannel.Result.class); + mockResult = mock(Messages.VoidResult.class); mockCamera = mock(Camera.class); handler.camera = mockCamera; } @Test public void shouldNotImplementLifecycleObserverInterface() { - Class methodCallHandlerClass = MethodCallHandlerImpl.class; + Class methodCallHandlerClass = CameraApiImpl.class; assertFalse(LifecycleObserver.class.isAssignableFrom(methodCallHandlerClass)); } @@ -50,10 +49,9 @@ public void shouldNotImplementLifecycleObserverInterface() { @Test public void onMethodCall_pausePreview_shouldPausePreviewAndSendSuccessResult() throws CameraAccessException { - handler.onMethodCall(new MethodCall("pausePreview", null), mockResult); + handler.pausePreview(); verify(mockCamera, times(1)).pausePreview(); - verify(mockResult, times(1)).success(null); } @Test @@ -61,16 +59,13 @@ public void onMethodCall_pausePreview_shouldSendErrorResultOnCameraAccessExcepti throws CameraAccessException { doThrow(new CameraAccessException(0)).when(mockCamera).pausePreview(); - handler.onMethodCall(new MethodCall("pausePreview", null), mockResult); - - verify(mockResult, times(1)).error("CameraAccess", null, null); + assertThrows(Messages.FlutterError.class, () -> handler.pausePreview()); } @Test public void onMethodCall_resumePreview_shouldResumePreviewAndSendSuccessResult() { - handler.onMethodCall(new MethodCall("resumePreview", null), mockResult); + handler.resumePreview(); verify(mockCamera, times(1)).resumePreview(); - verify(mockResult, times(1)).success(null); } } diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java index d2d90b7beca..56a3a4562ad 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java @@ -7,6 +7,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -30,7 +31,6 @@ import androidx.lifecycle.LifecycleObserver; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.camera.features.CameraFeatureFactory; import io.flutter.plugins.camera.features.CameraFeatures; import io.flutter.plugins.camera.features.Point; @@ -59,13 +59,39 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentMatcher; import org.mockito.MockedConstruction; import org.mockito.MockedStatic; import org.mockito.Mockito; +/** + * As Pigeon-generated class, including FlutterError, do not implement equality, this helper class + * simplifies matching arguments passed to Result/VoidResult.error in unit tests. + */ +class FlutterErrorMatcher implements ArgumentMatcher { + + FlutterErrorMatcher(String code, String message, Object details) { + this.code = code; + this.message = message; + this.details = details; + } + + final String code; + final String message; + final Object details; + + @Override + public boolean matches(Messages.FlutterError argument) { + return Objects.equals(code, argument.code) + && Objects.equals(message, argument.getMessage()) + && Objects.equals(details, argument.details); + } +} + class FakeCameraDeviceWrapper implements CameraDeviceWrapper { final List captureRequests; @Nullable final CameraCaptureSession session; @@ -324,21 +350,21 @@ public void getMinZoomLevel() { public void setExposureMode_shouldUpdateExposureLockFeature() { ExposureLockFeature mockExposureLockFeature = mockCameraFeatureFactory.createExposureLockFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); ExposureMode exposureMode = ExposureMode.locked; camera.setExposureMode(mockResult, exposureMode); verify(mockExposureLockFeature, times(1)).setValue(exposureMode); - verify(mockResult, never()).error(any(), any(), any()); - verify(mockResult, times(1)).success(null); + verify(mockResult, never()).error(any()); + verify(mockResult, times(1)).success(); } @Test public void setExposureMode_shouldUpdateBuilder() { ExposureLockFeature mockExposureLockFeature = mockCameraFeatureFactory.createExposureLockFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); ExposureMode exposureMode = ExposureMode.locked; camera.setExposureMode(mockResult, exposureMode); @@ -349,16 +375,19 @@ public void setExposureMode_shouldUpdateBuilder() { @Test public void setExposureMode_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); ExposureMode exposureMode = ExposureMode.locked; when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setExposureMode(mockResult, exposureMode); - verify(mockResult, never()).success(any()); + verify(mockResult, never()).success(); verify(mockResult, times(1)) - .error("setExposureModeFailed", "Could not set exposure mode.", null); + .error( + argThat( + new FlutterErrorMatcher( + "setExposureModeFailed", "Could not set exposure mode.", null))); } @Test @@ -367,14 +396,14 @@ public void setExposurePoint_shouldUpdateExposurePointFeature() { ExposurePointFeature mockExposurePointFeature = mockCameraFeatureFactory.createExposurePointFeature( mockCameraProperties, mockSensorOrientationFeature); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); Point point = new Point(42d, 42d); camera.setExposurePoint(mockResult, point); verify(mockExposurePointFeature, times(1)).setValue(point); - verify(mockResult, never()).error(any(), any(), any()); - verify(mockResult, times(1)).success(null); + verify(mockResult, never()).error(any()); + verify(mockResult, times(1)).success(); } @Test @@ -383,7 +412,7 @@ public void setExposurePoint_shouldUpdateBuilder() { ExposurePointFeature mockExposurePointFeature = mockCameraFeatureFactory.createExposurePointFeature( mockCameraProperties, mockSensorOrientationFeature); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); Point point = new Point(42d, 42d); camera.setExposurePoint(mockResult, point); @@ -394,37 +423,40 @@ public void setExposurePoint_shouldUpdateBuilder() { @Test public void setExposurePoint_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); Point point = new Point(42d, 42d); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setExposurePoint(mockResult, point); - verify(mockResult, never()).success(any()); + verify(mockResult, never()).success(); verify(mockResult, times(1)) - .error("setExposurePointFailed", "Could not set exposure point.", null); + .error( + argThat( + new FlutterErrorMatcher( + "setExposurePointFailed", "Could not set exposure point.", null))); } @Test public void setFlashMode_shouldUpdateFlashFeature() { FlashFeature mockFlashFeature = mockCameraFeatureFactory.createFlashFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); FlashMode flashMode = FlashMode.always; camera.setFlashMode(mockResult, flashMode); verify(mockFlashFeature, times(1)).setValue(flashMode); - verify(mockResult, never()).error(any(), any(), any()); - verify(mockResult, times(1)).success(null); + verify(mockResult, never()).error(any()); + verify(mockResult, times(1)).success(); } @Test public void setFlashMode_shouldUpdateBuilder() { FlashFeature mockFlashFeature = mockCameraFeatureFactory.createFlashFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); FlashMode flashMode = FlashMode.always; camera.setFlashMode(mockResult, flashMode); @@ -435,15 +467,18 @@ public void setFlashMode_shouldUpdateBuilder() { @Test public void setFlashMode_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); FlashMode flashMode = FlashMode.always; when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setFlashMode(mockResult, flashMode); - verify(mockResult, never()).success(any()); - verify(mockResult, times(1)).error("setFlashModeFailed", "Could not set flash mode.", null); + verify(mockResult, never()).success(); + verify(mockResult, times(1)) + .error( + argThat( + new FlutterErrorMatcher("setFlashModeFailed", "Could not set flash mode.", null))); } @Test @@ -454,15 +489,15 @@ public void setFocusPoint_shouldUpdateFocusPointFeature() { mockCameraProperties, mockSensorOrientationFeature); AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); Point point = new Point(42d, 42d); when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto); camera.setFocusPoint(mockResult, point); verify(mockFocusPointFeature, times(1)).setValue(point); - verify(mockResult, never()).error(any(), any(), any()); - verify(mockResult, times(1)).success(null); + verify(mockResult, never()).error(any()); + verify(mockResult, times(1)).success(); } @Test @@ -473,7 +508,7 @@ public void setFocusPoint_shouldUpdateBuilder() { mockCameraProperties, mockSensorOrientationFeature); AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); Point point = new Point(42d, 42d); when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto); @@ -487,7 +522,7 @@ public void setFocusPoint_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); Point point = new Point(42d, 42d); when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) @@ -495,15 +530,19 @@ public void setFocusPoint_shouldCallErrorOnResultOnCameraAccessException() camera.setFocusPoint(mockResult, point); - verify(mockResult, never()).success(any()); - verify(mockResult, times(1)).error("setFocusPointFailed", "Could not set focus point.", null); + verify(mockResult, never()).success(); + verify(mockResult, times(1)) + .error( + argThat( + new FlutterErrorMatcher( + "setFocusPointFailed", "Could not set focus point.", null))); } @Test public void setZoomLevel_shouldUpdateZoomLevelFeature() throws CameraAccessException { ZoomLevelFeature mockZoomLevelFeature = mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); float zoomLevel = 1.0f; when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel); @@ -513,15 +552,15 @@ public void setZoomLevel_shouldUpdateZoomLevelFeature() throws CameraAccessExcep camera.setZoomLevel(mockResult, zoomLevel); verify(mockZoomLevelFeature, times(1)).setValue(zoomLevel); - verify(mockResult, never()).error(any(), any(), any()); - verify(mockResult, times(1)).success(null); + verify(mockResult, never()).error(any()); + verify(mockResult, times(1)).success(); } @Test public void setZoomLevel_shouldUpdateBuilder() throws CameraAccessException { ZoomLevelFeature mockZoomLevelFeature = mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); float zoomLevel = 1.0f; when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel); @@ -538,7 +577,7 @@ public void setZoomLevel_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { ZoomLevelFeature mockZoomLevelFeature = mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + Messages.VoidResult mockResult = mock(Messages.VoidResult.class); float zoomLevel = 1.0f; when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel); @@ -549,47 +588,38 @@ public void setZoomLevel_shouldCallErrorOnResultOnCameraAccessException() camera.setZoomLevel(mockResult, zoomLevel); - verify(mockResult, never()).success(any()); - verify(mockResult, times(1)).error("setZoomLevelFailed", "Could not set zoom level.", null); + verify(mockResult, never()).success(); + verify(mockResult, times(1)) + .error( + argThat( + new FlutterErrorMatcher("setZoomLevelFailed", "Could not set zoom level.", null))); } @Test - public void pauseVideoRecording_shouldSendNullResultWhenNotRecording() { + public void pauseVideoRecording_shouldNotThrowWhenNotRecording() { camera.recordingVideo = false; - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - - camera.pauseVideoRecording(mockResult); - verify(mockResult, times(1)).success(null); - verify(mockResult, never()).error(any(), any(), any()); + camera.pauseVideoRecording(); } @Test public void pauseVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); camera.mediaRecorder = mockMediaRecorder; camera.recordingVideo = true; SdkCapabilityChecker.SDK_VERSION = 24; - camera.pauseVideoRecording(mockResult); + camera.pauseVideoRecording(); verify(mockMediaRecorder, times(1)).pause(); - verify(mockResult, times(1)).success(null); - verify(mockResult, never()).error(any(), any(), any()); } @Test public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThenN() { camera.recordingVideo = true; SdkCapabilityChecker.SDK_VERSION = 23; - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - camera.pauseVideoRecording(mockResult); - - verify(mockResult, times(1)) - .error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); - verify(mockResult, never()).success(any()); + assertThrows(Messages.FlutterError.class, camera::pauseVideoRecording); } @Test @@ -604,43 +634,30 @@ public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCo doThrow(expectedException).when(mockMediaRecorder).pause(); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - - camera.pauseVideoRecording(mockResult); - - verify(mockResult, times(1)).error("videoRecordingFailed", "Test error message", null); - verify(mockResult, never()).success(any()); + assertThrows(Messages.FlutterError.class, camera::pauseVideoRecording); } @Test - public void resumeVideoRecording_shouldSendNullResultWhenNotRecording() { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + public void resumeVideoRecording_shouldNotThrowWhenNotRecording() { camera.recordingVideo = false; - camera.resumeVideoRecording(mockResult); - - verify(mockResult, times(1)).success(null); - verify(mockResult, never()).error(any(), any(), any()); + camera.resumeVideoRecording(); } @Test public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); camera.mediaRecorder = mockMediaRecorder; camera.recordingVideo = true; SdkCapabilityChecker.SDK_VERSION = 24; - camera.resumeVideoRecording(mockResult); + camera.resumeVideoRecording(); verify(mockMediaRecorder, times(1)).resume(); - verify(mockResult, times(1)).success(null); - verify(mockResult, never()).error(any(), any(), any()); } @Test public void setDescriptionWhileRecording_errorsWhenUnsupported() { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); camera.mediaRecorder = mockMediaRecorder; @@ -649,18 +666,13 @@ public void setDescriptionWhileRecording_errorsWhenUnsupported() { SdkCapabilityChecker.SDK_VERSION = Build.VERSION_CODES.LOLLIPOP; final CameraProperties newCameraProperties = mock(CameraProperties.class); - camera.setDescriptionWhileRecording(mockResult, newCameraProperties); - - verify(mockResult, times(1)) - .error( - eq("setDescriptionWhileRecordingFailed"), - eq("Device does not support switching the camera while recording"), - eq(null)); + assertThrows( + Messages.FlutterError.class, + () -> camera.setDescriptionWhileRecording(newCameraProperties)); } @Test public void setDescriptionWhileRecording_succeedsWhenSupported() { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); camera.mediaRecorder = mockMediaRecorder; @@ -669,10 +681,7 @@ public void setDescriptionWhileRecording_succeedsWhenSupported() { SdkCapabilityChecker.SDK_VERSION = Build.VERSION_CODES.O; final CameraProperties newCameraProperties = mock(CameraProperties.class); - camera.setDescriptionWhileRecording(mockResult, newCameraProperties); - - verify(mockResult, times(1)).success(null); - verify(mockResult, never()).error(any(), any(), any()); + camera.setDescriptionWhileRecording(newCameraProperties); } @Test @@ -784,14 +793,11 @@ public void startPreviewWithImageStream_shouldPullStreamsFromImageReaders() @Test public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.recordingVideo = false; final CameraProperties newCameraProperties = mock(CameraProperties.class); - camera.setDescriptionWhileRecording(mockResult, newCameraProperties); - - verify(mockResult, times(1)) - .error("setDescriptionWhileRecordingFailed", "Device was not recording", null); - verify(mockResult, never()).success(any()); + assertThrows( + Messages.FlutterError.class, + () -> camera.setDescriptionWhileRecording(newCameraProperties)); } @Test @@ -800,13 +806,7 @@ public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() { camera.recordingVideo = true; SdkCapabilityChecker.SDK_VERSION = 23; - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - - camera.resumeVideoRecording(mockResult); - - verify(mockResult, times(1)) - .error("videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); - verify(mockResult, never()).success(any()); + assertThrows(Messages.FlutterError.class, camera::resumeVideoRecording); } @Test @@ -821,41 +821,32 @@ public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() { doThrow(expectedException).when(mockMediaRecorder).resume(); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - - camera.resumeVideoRecording(mockResult); - - verify(mockResult, times(1)).error("videoRecordingFailed", "Test error message", null); - verify(mockResult, never()).success(any()); + assertThrows(Messages.FlutterError.class, camera::resumeVideoRecording); } @Test public void setFocusMode_shouldUpdateAutoFocusFeature() { AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - camera.setFocusMode(mockResult, FocusMode.auto); + camera.setFocusMode(FocusMode.auto); verify(mockAutoFocusFeature, times(1)).setValue(FocusMode.auto); - verify(mockResult, never()).error(any(), any(), any()); - verify(mockResult, times(1)).success(null); } @Test public void setFocusMode_shouldUpdateBuilder() { AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - camera.setFocusMode(mockResult, FocusMode.auto); + camera.setFocusMode(FocusMode.auto); verify(mockAutoFocusFeature, times(1)).updateBuilder(any()); } @Test public void setFocusMode_shouldUnlockAutoFocusForAutoMode() { - camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto); + camera.setFocusMode(FocusMode.auto); verify(mockPreviewRequestBuilder, times(1)) .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); verify(mockPreviewRequestBuilder, times(1)) @@ -865,7 +856,7 @@ public void setFocusMode_shouldUnlockAutoFocusForAutoMode() { @Test public void setFocusMode_shouldSkipUnlockAutoFocusWhenNullCaptureSession() { camera.captureSession = null; - camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto); + camera.setFocusMode(FocusMode.auto); verify(mockPreviewRequestBuilder, never()) .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); verify(mockPreviewRequestBuilder, never()) @@ -877,7 +868,7 @@ public void setFocusMode_shouldSendErrorEventOnUnlockAutoFocusCameraAccessExcept throws CameraAccessException { when(mockCaptureSession.capture(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); - camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto); + camera.setFocusMode(FocusMode.auto); verify(mockDartMessenger, times(1)).sendCameraErrorEvent(any()); } @@ -896,16 +887,15 @@ public void startVideoRecording_shouldPullStreamsFromMediaRecorderAndImageReader cameraSpy.pictureImageReader = mockPictureImageReader; CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); cameraSpy.cameraDevice = fakeCamera; - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = cameraSpy.flutterTexture; ResolutionFeature resolutionFeature = mockCameraFeatureFactory.mockResolutionFeature; when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture); when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); - doNothing().when(cameraSpy).prepareRecording(mockResult); + doNothing().when(cameraSpy).prepareRecording(); - cameraSpy.startVideoRecording(mockResult, null); + cameraSpy.startVideoRecording(null); verify(mockMediaRecorder, times(1)) .getSurface(); // stream pulled from media recorder's surface. verify( @@ -916,7 +906,7 @@ public void startVideoRecording_shouldPullStreamsFromMediaRecorderAndImageReader @Test public void setFocusMode_shouldLockAutoFocusForLockedMode() throws CameraAccessException { - camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked); + camera.setFocusMode(FocusMode.locked); verify(mockPreviewRequestBuilder, times(1)) .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); verify(mockCaptureSession, times(1)).capture(any(), any(), any()); @@ -926,7 +916,7 @@ public void setFocusMode_shouldLockAutoFocusForLockedMode() throws CameraAccessE @Test public void setFocusMode_shouldSkipLockAutoFocusWhenNullCaptureSession() { camera.captureSession = null; - camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked); + camera.setFocusMode(FocusMode.locked); verify(mockPreviewRequestBuilder, never()) .set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); } @@ -936,36 +926,32 @@ public void setFocusMode_shouldSendErrorEventOnLockAutoFocusCameraAccessExceptio throws CameraAccessException { when(mockCaptureSession.capture(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); - camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked); + camera.setFocusMode(FocusMode.locked); verify(mockDartMessenger, times(1)).sendCameraErrorEvent(any()); } @Test public void setFocusMode_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); - camera.setFocusMode(mockResult, FocusMode.locked); - - verify(mockResult, never()).success(any()); - verify(mockResult, times(1)) - .error("setFocusModeFailed", "Error setting focus mode: null", null); + assertThrows(Messages.FlutterError.class, () -> camera.setFocusMode(FocusMode.locked)); } @Test public void setExposureOffset_shouldUpdateExposureOffsetFeature() { ExposureOffsetFeature mockExposureOffsetFeature = mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + @SuppressWarnings("unchecked") + Messages.Result mockResult = mock(Messages.Result.class); when(mockExposureOffsetFeature.getValue()).thenReturn(1.0); camera.setExposureOffset(mockResult, 1.0); verify(mockExposureOffsetFeature, times(1)).setValue(1.0); - verify(mockResult, never()).error(any(), any(), any()); + verify(mockResult, never()).error(any()); verify(mockResult, times(1)).success(1.0); } @@ -973,7 +959,8 @@ public void setExposureOffset_shouldUpdateExposureOffsetFeature() { public void setExposureOffset_shouldAndUpdateBuilder() { ExposureOffsetFeature mockExposureOffsetFeature = mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + @SuppressWarnings("unchecked") + Messages.Result mockResult = mock(Messages.Result.class); camera.setExposureOffset(mockResult, 1.0); @@ -983,7 +970,8 @@ public void setExposureOffset_shouldAndUpdateBuilder() { @Test public void setExposureOffset_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + @SuppressWarnings("unchecked") + Messages.Result mockResult = mock(Messages.Result.class); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); @@ -991,7 +979,10 @@ public void setExposureOffset_shouldCallErrorOnResultOnCameraAccessException() verify(mockResult, never()).success(any()); verify(mockResult, times(1)) - .error("setExposureOffsetFailed", "Could not set exposure offset.", null); + .error( + argThat( + new FlutterErrorMatcher( + "setExposureOffsetFailed", "Could not set exposure offset.", null))); } @Test @@ -1253,7 +1244,6 @@ public void startVideoRecording_shouldApplySettingsToMediaRecorder() new FakeCameraDeviceWrapper(mockRequestBuilders, mockCaptureSession); camera.cameraDevice = fakeCamera; - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = camera.flutterTexture; @@ -1265,7 +1255,7 @@ public void startVideoRecording_shouldApplySettingsToMediaRecorder() assertNotNull(resolutionFeature); when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); - camera.startVideoRecording(mockResult, null); + camera.startVideoRecording(null); //region Check that FPS parameter affects AE range at which the camera captures frames. assertEquals(camera.cameraFeatures.getFpsRange().getValue().getLower(), Integer.valueOf(fps)); diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java index bf6e6c4d203..fb3588e4589 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java @@ -12,58 +12,21 @@ import android.app.Activity; import android.content.Context; +import android.graphics.ImageFormat; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import io.flutter.embedding.engine.systemchannels.PlatformChannel; +import io.flutter.plugins.camera.features.autofocus.FocusMode; +import io.flutter.plugins.camera.features.exposurelock.ExposureMode; +import io.flutter.plugins.camera.features.flash.FlashMode; +import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import java.util.List; import org.junit.Test; public class CameraUtilsTest { - @Test - public void serializeDeviceOrientation_serializesCorrectly() { - assertEquals( - "portraitUp", - CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_UP)); - assertEquals( - "portraitDown", - CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_DOWN)); - assertEquals( - "landscapeLeft", - CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT)); - assertEquals( - "landscapeRight", - CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT)); - } - - @Test(expected = UnsupportedOperationException.class) - public void serializeDeviceOrientation_throws_for_null() { - CameraUtils.serializeDeviceOrientation(null); - } - - @Test - public void deserializeDeviceOrientation_deserializesCorrectly() { - assertEquals( - PlatformChannel.DeviceOrientation.PORTRAIT_UP, - CameraUtils.deserializeDeviceOrientation("portraitUp")); - assertEquals( - PlatformChannel.DeviceOrientation.PORTRAIT_DOWN, - CameraUtils.deserializeDeviceOrientation("portraitDown")); - assertEquals( - PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT, - CameraUtils.deserializeDeviceOrientation("landscapeLeft")); - assertEquals( - PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT, - CameraUtils.deserializeDeviceOrientation("landscapeRight")); - } - - @Test(expected = UnsupportedOperationException.class) - public void deserializeDeviceOrientation_throwsForNull() { - CameraUtils.deserializeDeviceOrientation(null); - } - @Test public void getAvailableCameras_retrievesValidCameras() throws CameraAccessException, NumberFormatException { @@ -99,4 +62,113 @@ public void getAvailableCameras_retrievesValidCameras() assertEquals( availableCameras.get(1).getLensDirection(), Messages.PlatformCameraLensDirection.EXTERNAL); } + + @Test + public void orientationToPigeonTest() { + assertEquals( + CameraUtils.orientationToPigeon(PlatformChannel.DeviceOrientation.PORTRAIT_UP), + Messages.PlatformDeviceOrientation.PORTRAIT_UP); + assertEquals( + CameraUtils.orientationToPigeon(PlatformChannel.DeviceOrientation.PORTRAIT_DOWN), + Messages.PlatformDeviceOrientation.PORTRAIT_DOWN); + assertEquals( + CameraUtils.orientationToPigeon(PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT), + Messages.PlatformDeviceOrientation.LANDSCAPE_LEFT); + assertEquals( + CameraUtils.orientationToPigeon(PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT), + Messages.PlatformDeviceOrientation.LANDSCAPE_RIGHT); + } + + @Test + public void orientationFromPigeonTest() { + assertEquals( + CameraUtils.orientationFromPigeon(Messages.PlatformDeviceOrientation.PORTRAIT_UP), + PlatformChannel.DeviceOrientation.PORTRAIT_UP); + assertEquals( + CameraUtils.orientationFromPigeon(Messages.PlatformDeviceOrientation.PORTRAIT_DOWN), + PlatformChannel.DeviceOrientation.PORTRAIT_DOWN); + assertEquals( + CameraUtils.orientationFromPigeon(Messages.PlatformDeviceOrientation.LANDSCAPE_LEFT), + PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); + assertEquals( + CameraUtils.orientationFromPigeon(Messages.PlatformDeviceOrientation.LANDSCAPE_RIGHT), + PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT); + } + + @Test + public void focusModeToPigeonTest() { + assertEquals(CameraUtils.focusModeToPigeon(FocusMode.auto), Messages.PlatformFocusMode.AUTO); + assertEquals( + CameraUtils.focusModeToPigeon(FocusMode.locked), Messages.PlatformFocusMode.LOCKED); + } + + @Test + public void focusModeFromPigeonTest() { + assertEquals(CameraUtils.focusModeFromPigeon(Messages.PlatformFocusMode.AUTO), FocusMode.auto); + assertEquals( + CameraUtils.focusModeFromPigeon(Messages.PlatformFocusMode.LOCKED), FocusMode.locked); + } + + @Test + public void exposureModeToPigeonTest() { + assertEquals( + CameraUtils.exposureModeToPigeon(ExposureMode.auto), Messages.PlatformExposureMode.AUTO); + assertEquals( + CameraUtils.exposureModeToPigeon(ExposureMode.locked), + Messages.PlatformExposureMode.LOCKED); + } + + @Test + public void exposureModeFromPigeonTest() { + assertEquals( + CameraUtils.exposureModeFromPigeon(Messages.PlatformExposureMode.AUTO), ExposureMode.auto); + assertEquals( + CameraUtils.exposureModeFromPigeon(Messages.PlatformExposureMode.LOCKED), + ExposureMode.locked); + } + + @Test + public void resolutionPresetFromPigeonTest() { + assertEquals( + CameraUtils.resolutionPresetFromPigeon(Messages.PlatformResolutionPreset.LOW), + ResolutionPreset.low); + assertEquals( + CameraUtils.resolutionPresetFromPigeon(Messages.PlatformResolutionPreset.MEDIUM), + ResolutionPreset.medium); + assertEquals( + CameraUtils.resolutionPresetFromPigeon(Messages.PlatformResolutionPreset.HIGH), + ResolutionPreset.high); + assertEquals( + CameraUtils.resolutionPresetFromPigeon(Messages.PlatformResolutionPreset.VERY_HIGH), + ResolutionPreset.veryHigh); + assertEquals( + CameraUtils.resolutionPresetFromPigeon(Messages.PlatformResolutionPreset.ULTRA_HIGH), + ResolutionPreset.ultraHigh); + assertEquals( + CameraUtils.resolutionPresetFromPigeon(Messages.PlatformResolutionPreset.MAX), + ResolutionPreset.max); + } + + @Test + public void imageFormatGroupFromPigeonTest() { + assertEquals( + CameraUtils.imageFormatGroupFromPigeon(Messages.PlatformImageFormatGroup.YUV420).intValue(), + ImageFormat.YUV_420_888); + assertEquals( + CameraUtils.imageFormatGroupFromPigeon(Messages.PlatformImageFormatGroup.JPEG).intValue(), + ImageFormat.JPEG); + assertEquals( + CameraUtils.imageFormatGroupFromPigeon(Messages.PlatformImageFormatGroup.NV21).intValue(), + ImageFormat.NV21); + } + + @Test + public void flashModeFromPigeonTest() { + assertEquals(CameraUtils.flashModeFromPigeon(Messages.PlatformFlashMode.AUTO), FlashMode.auto); + assertEquals( + CameraUtils.flashModeFromPigeon(Messages.PlatformFlashMode.ALWAYS), FlashMode.always); + assertEquals(CameraUtils.flashModeFromPigeon(Messages.PlatformFlashMode.OFF), FlashMode.off); + assertEquals( + CameraUtils.flashModeFromPigeon(Messages.PlatformFlashMode.TORCH), FlashMode.torch); + } } diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index 95f339779ea..975c6c9be02 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; @@ -15,9 +14,6 @@ import 'messages.g.dart'; import 'type_conversion.dart'; import 'utils.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/camera_android'); - /// The Android implementation of [CameraPlatform] that uses method channels. class AndroidCamera extends CameraPlatform { /// Creates a new [CameraPlatform] instance. @@ -105,22 +101,8 @@ class AndroidCamera extends CameraPlatform { MediaSettings? mediaSettings, ) async { try { - final ResolutionPreset? resolutionPreset = - mediaSettings?.resolutionPreset; - - final Map? reply = await _channel - .invokeMapMethod('create', { - 'cameraName': cameraDescription.name, - 'resolutionPreset': resolutionPreset != null - ? _serializeResolutionPreset(resolutionPreset) - : null, - 'fps': mediaSettings?.fps, - 'videoBitrate': mediaSettings?.videoBitrate, - 'audioBitrate': mediaSettings?.audioBitrate, - 'enableAudio': mediaSettings?.enableAudio ?? false, - }); - - return reply!['cameraId']! as int; + return await _hostApi.create( + cameraDescription.name, mediaSettingsToPlatform(mediaSettings)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -130,38 +112,23 @@ class AndroidCamera extends CameraPlatform { Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, - }) { + }) async { hostCameraHandlers.putIfAbsent(cameraId, () => HostCameraMessageHandler(cameraId, cameraEventStreamController)); final Completer completer = Completer(); - onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) { + unawaited(onCameraInitialized(cameraId) + .first + .then((CameraInitializedEvent value) { completer.complete(); - }); + })); - _channel.invokeMapMethod( - 'initialize', - { - 'cameraId': cameraId, - 'imageFormatGroup': imageFormatGroup.name(), - }, - ).catchError( - // TODO(srawlins): This should return a value of the future's type. This - // will fail upcoming analysis checks with - // https://github.com/flutter/flutter/issues/105750. - // ignore: body_might_complete_normally_catch_error - (Object error, StackTrace stackTrace) { - if (error is! PlatformException) { - // ignore: only_throw_errors - throw error; - } - completer.completeError( - CameraException(error.code, error.message), - stackTrace, - ); - }, - ); + try { + await _hostApi.initialize(imageFormatGroupToPlatform(imageFormatGroup)); + } on PlatformException catch (e, s) { + completer.completeError(CameraException(e.code, e.message), s); + } return completer.future; } @@ -172,10 +139,7 @@ class AndroidCamera extends CameraPlatform { hostCameraHandlers.remove(cameraId); handler?.dispose(); - await _channel.invokeMethod( - 'dispose', - {'cameraId': cameraId}, - ); + await _hostApi.dispose(); } @override @@ -214,43 +178,24 @@ class AndroidCamera extends CameraPlatform { int cameraId, DeviceOrientation orientation, ) async { - await _channel.invokeMethod( - 'lockCaptureOrientation', - { - 'cameraId': cameraId, - 'orientation': serializeDeviceOrientation(orientation) - }, - ); + await _hostApi + .lockCaptureOrientation(deviceOrientationToPlatform(orientation)); } @override Future unlockCaptureOrientation(int cameraId) async { - await _channel.invokeMethod( - 'unlockCaptureOrientation', - {'cameraId': cameraId}, - ); + await _hostApi.unlockCaptureOrientation(); } @override Future takePicture(int cameraId) async { - final String? path = await _channel.invokeMethod( - 'takePicture', - {'cameraId': cameraId}, - ); - - if (path == null) { - throw CameraException( - 'INVALID_PATH', - 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', - ); - } - + final String path = await _hostApi.takePicture(); return XFile(path); } + // This optimization is unnecessary on Android. @override - Future prepareForVideoRecording() => - _channel.invokeMethod('prepareForVideoRecording'); + Future prepareForVideoRecording() async {} @override Future startVideoRecording(int cameraId, @@ -261,13 +206,7 @@ class AndroidCamera extends CameraPlatform { @override Future startVideoCapturing(VideoCaptureOptions options) async { - await _channel.invokeMethod( - 'startVideoRecording', - { - 'cameraId': options.cameraId, - 'enableStream': options.streamCallback != null, - }, - ); + await _hostApi.startVideoRecording(options.streamCallback != null); if (options.streamCallback != null) { _installStreamController().stream.listen(options.streamCallback); @@ -277,33 +216,17 @@ class AndroidCamera extends CameraPlatform { @override Future stopVideoRecording(int cameraId) async { - final String? path = await _channel.invokeMethod( - 'stopVideoRecording', - {'cameraId': cameraId}, - ); - - if (path == null) { - throw CameraException( - 'INVALID_PATH', - 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', - ); - } - + final String path = await _hostApi.stopVideoRecording(); return XFile(path); } @override - Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( - 'pauseVideoRecording', - {'cameraId': cameraId}, - ); + Future pauseVideoRecording(int cameraId) => + _hostApi.pauseVideoRecording(); @override Future resumeVideoRecording(int cameraId) => - _channel.invokeMethod( - 'resumeVideoRecording', - {'cameraId': cameraId}, - ); + _hostApi.resumeVideoRecording(); @override Stream onStreamedFrameAvailable(int cameraId, @@ -328,7 +251,7 @@ class AndroidCamera extends CameraPlatform { } Future _startPlatformStream() async { - await _channel.invokeMethod('startImageStream'); + await _hostApi.startImageStream(); _startStreamListener(); } @@ -343,7 +266,7 @@ class AndroidCamera extends CameraPlatform { } FutureOr _onFrameStreamCancel() async { - await _channel.invokeMethod('stopImageStream'); + await _hostApi.stopImageStream(); await _platformImageStreamSubscription?.cancel(); _platformImageStreamSubscription = null; _frameStreamController = null; @@ -356,139 +279,66 @@ class AndroidCamera extends CameraPlatform { @override Future setFlashMode(int cameraId, FlashMode mode) => - _channel.invokeMethod( - 'setFlashMode', - { - 'cameraId': cameraId, - 'mode': _serializeFlashMode(mode), - }, - ); + _hostApi.setFlashMode(flashModeToPlatform(mode)); @override Future setExposureMode(int cameraId, ExposureMode mode) => - _channel.invokeMethod( - 'setExposureMode', - { - 'cameraId': cameraId, - 'mode': serializeExposureMode(mode), - }, - ); + _hostApi.setExposureMode(exposureModeToPlatform(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, - }, - ); + return _hostApi.setExposurePoint(pointToPlatform(point)); } @override Future getMinExposureOffset(int cameraId) async { - final double? minExposureOffset = await _channel.invokeMethod( - 'getMinExposureOffset', - {'cameraId': cameraId}, - ); - - return minExposureOffset!; + return _hostApi.getMinExposureOffset(); } @override Future getMaxExposureOffset(int cameraId) async { - final double? maxExposureOffset = await _channel.invokeMethod( - 'getMaxExposureOffset', - {'cameraId': cameraId}, - ); - - return maxExposureOffset!; + return _hostApi.getMaxExposureOffset(); } @override Future getExposureOffsetStepSize(int cameraId) async { - final double? stepSize = await _channel.invokeMethod( - 'getExposureOffsetStepSize', - {'cameraId': cameraId}, - ); - - return stepSize!; + return _hostApi.getExposureOffsetStepSize(); } @override Future setExposureOffset(int cameraId, double offset) async { - final double? appliedOffset = await _channel.invokeMethod( - 'setExposureOffset', - { - 'cameraId': cameraId, - 'offset': offset, - }, - ); - - return appliedOffset!; + return _hostApi.setExposureOffset(offset); } @override Future setFocusMode(int cameraId, FocusMode mode) => - _channel.invokeMethod( - 'setFocusMode', - { - 'cameraId': cameraId, - 'mode': serializeFocusMode(mode), - }, - ); + _hostApi.setFocusMode(focusModeToPlatform(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, - }, - ); + return _hostApi.setFocusPoint(pointToPlatform(point)); } @override Future getMaxZoomLevel(int cameraId) async { - final double? maxZoomLevel = await _channel.invokeMethod( - 'getMaxZoomLevel', - {'cameraId': cameraId}, - ); - - return maxZoomLevel!; + return _hostApi.getMaxZoomLevel(); } @override Future getMinZoomLevel(int cameraId) async { - final double? minZoomLevel = await _channel.invokeMethod( - 'getMinZoomLevel', - {'cameraId': cameraId}, - ); - - return minZoomLevel!; + return _hostApi.getMinZoomLevel(); } @override Future setZoomLevel(int cameraId, double zoom) async { try { - await _channel.invokeMethod( - 'setZoomLevel', - { - 'cameraId': cameraId, - 'zoom': zoom, - }, - ); + await _hostApi.setZoomLevel(zoom); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -496,81 +346,24 @@ class AndroidCamera extends CameraPlatform { @override Future pausePreview(int cameraId) async { - await _channel.invokeMethod( - 'pausePreview', - {'cameraId': cameraId}, - ); + await _hostApi.pausePreview(); } @override Future resumePreview(int cameraId) async { - await _channel.invokeMethod( - 'resumePreview', - {'cameraId': cameraId}, - ); + await _hostApi.resumePreview(); } @override Future setDescriptionWhileRecording( CameraDescription description) async { - await _channel.invokeMethod( - 'setDescriptionWhileRecording', - { - 'cameraName': description.name, - }, - ); + await _hostApi.setDescriptionWhileRecording(description.name); } @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); } - - /// Returns the flash mode as a String. - String _serializeFlashMode(FlashMode flashMode) { - switch (flashMode) { - case FlashMode.off: - return 'off'; - case FlashMode.auto: - return 'auto'; - case FlashMode.always: - return 'always'; - case FlashMode.torch: - return 'torch'; - } - // The enum comes from a different package, which could get a new value at - // any time, so provide a fallback that ensures this won't break when used - // with a version that contains new values. This is deliberately outside - // the switch rather than a `default` so that the linter will flag the - // switch as needing an update. - // ignore: dead_code - return 'off'; - } - - /// Returns the resolution preset as a String. - String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { - switch (resolutionPreset) { - case ResolutionPreset.max: - return 'max'; - case ResolutionPreset.ultraHigh: - return 'ultraHigh'; - case ResolutionPreset.veryHigh: - return 'veryHigh'; - case ResolutionPreset.high: - return 'high'; - case ResolutionPreset.medium: - return 'medium'; - case ResolutionPreset.low: - return 'low'; - } - // The enum comes from a different package, which could get a new value at - // any time, so provide a fallback that ensures this won't break when used - // with a version that contains new values. This is deliberately outside - // the switch rather than a `default` so that the linter will flag the - // switch as needing an update. - // ignore: dead_code - return 'max'; - } } /// Handles callbacks from the platform host that are not camera-specific. diff --git a/packages/camera/camera_android/lib/src/messages.g.dart b/packages/camera/camera_android/lib/src/messages.g.dart index 87f810d67bb..81e21a01da3 100644 --- a/packages/camera/camera_android/lib/src/messages.g.dart +++ b/packages/camera/camera_android/lib/src/messages.g.dart @@ -56,6 +56,32 @@ enum PlatformFocusMode { locked, } +/// Pigeon equivalent of [ResolutionPreset]. +enum PlatformResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, +} + +/// Pigeon equivalent of [ImageFormatGroup]. +enum PlatformImageFormatGroup { + /// The default for Android. + yuv420, + jpeg, + nv21, +} + +/// Pigeon equivalent of [FlashMode]. +enum PlatformFlashMode { + off, + auto, + always, + torch, +} + /// Pigeon equivalent of [CameraDescription]. class PlatformCameraDescription { PlatformCameraDescription({ @@ -157,6 +183,75 @@ class PlatformSize { } } +/// Pigeon equivalent of [Point]. +class PlatformPoint { + PlatformPoint({ + required this.x, + required this.y, + }); + + double x; + + double y; + + Object encode() { + return [ + x, + y, + ]; + } + + static PlatformPoint decode(Object result) { + result as List; + return PlatformPoint( + x: result[0]! as double, + y: result[1]! as double, + ); + } +} + +/// Pigeon equivalent of [MediaSettings]. +class PlatformMediaSettings { + PlatformMediaSettings({ + required this.resolutionPreset, + this.fps, + this.videoBitrate, + this.audioBitrate, + required this.enableAudio, + }); + + PlatformResolutionPreset resolutionPreset; + + int? fps; + + int? videoBitrate; + + int? audioBitrate; + + bool enableAudio; + + Object encode() { + return [ + resolutionPreset, + fps, + videoBitrate, + audioBitrate, + enableAudio, + ]; + } + + static PlatformMediaSettings decode(Object result) { + result as List; + return PlatformMediaSettings( + resolutionPreset: result[0]! as PlatformResolutionPreset, + fps: result[1] as int?, + videoBitrate: result[2] as int?, + audioBitrate: result[3] as int?, + enableAudio: result[4]! as bool, + ); + } +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -176,14 +271,29 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is PlatformFocusMode) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is PlatformCameraDescription) { + } else if (value is PlatformResolutionPreset) { buffer.putUint8(133); + writeValue(buffer, value.index); + } else if (value is PlatformImageFormatGroup) { + buffer.putUint8(134); + writeValue(buffer, value.index); + } else if (value is PlatformFlashMode) { + buffer.putUint8(135); + writeValue(buffer, value.index); + } else if (value is PlatformCameraDescription) { + buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is PlatformCameraState) { - buffer.putUint8(134); + buffer.putUint8(137); writeValue(buffer, value.encode()); } else if (value is PlatformSize) { - buffer.putUint8(135); + buffer.putUint8(138); + writeValue(buffer, value.encode()); + } else if (value is PlatformPoint) { + buffer.putUint8(139); + writeValue(buffer, value.encode()); + } else if (value is PlatformMediaSettings) { + buffer.putUint8(140); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -206,11 +316,24 @@ class _PigeonCodec extends StandardMessageCodec { final int? value = readValue(buffer) as int?; return value == null ? null : PlatformFocusMode.values[value]; case 133: - return PlatformCameraDescription.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformResolutionPreset.values[value]; case 134: - return PlatformCameraState.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformImageFormatGroup.values[value]; case 135: + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformFlashMode.values[value]; + case 136: + return PlatformCameraDescription.decode(readValue(buffer)!); + case 137: + return PlatformCameraState.decode(readValue(buffer)!); + case 138: return PlatformSize.decode(readValue(buffer)!); + case 139: + return PlatformPoint.decode(readValue(buffer)!); + case 140: + return PlatformMediaSettings.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -263,6 +386,737 @@ class CameraApi { .cast(); } } + + /// Creates a new camera with the given name and settings and returns its ID. + Future create( + String cameraName, PlatformMediaSettings mediaSettings) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.create$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = await pigeonVar_channel + .send([cameraName, mediaSettings]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as int?)!; + } + } + + /// Initializes the camera with the given ID for the given image format. + Future initialize(PlatformImageFormatGroup imageFormat) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.initialize$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([imageFormat]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Disposes of the camera with the given ID. + Future dispose() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.dispose$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Locks the camera with the given ID to the given orientation. + Future lockCaptureOrientation( + PlatformDeviceOrientation orientation) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.lockCaptureOrientation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([orientation]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Unlocks the orientation for the camera with the given ID. + Future unlockCaptureOrientation() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.unlockCaptureOrientation$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Takes a picture on the camera with the given ID and returns a path to the + /// resulting file. + Future takePicture() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.takePicture$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + /// Starts recording a video on the camera with the given ID. + Future startVideoRecording(bool enableStream) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.startVideoRecording$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([enableStream]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Ends video recording on the camera with the given ID and returns the path + /// to the resulting file. + Future stopVideoRecording() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.stopVideoRecording$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + /// Pauses video recording on the camera with the given ID. + Future pauseVideoRecording() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.pauseVideoRecording$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Resumes previously paused video recording on the camera with the given ID. + Future resumeVideoRecording() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.resumeVideoRecording$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Begins streaming frames from the camera. + Future startImageStream() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.startImageStream$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Stops streaming frames from the camera. + Future stopImageStream() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.stopImageStream$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the flash mode of the camera with the given ID. + Future setFlashMode(PlatformFlashMode flashMode) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setFlashMode$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([flashMode]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the exposure mode of the camera with the given ID. + Future setExposureMode(PlatformExposureMode exposureMode) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setExposureMode$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([exposureMode]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the exposure point of the camera with the given ID. + /// + /// A null value resets to the default exposure point. + Future setExposurePoint(PlatformPoint? point) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setExposurePoint$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([point]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Returns the minimum exposure offset of the camera with the given ID. + Future getMinExposureOffset() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.getMinExposureOffset$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as double?)!; + } + } + + /// Returns the maximum exposure offset of the camera with the given ID. + Future getMaxExposureOffset() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.getMaxExposureOffset$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as double?)!; + } + } + + /// Returns the exposure step size of the camera with the given ID. + Future getExposureOffsetStepSize() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.getExposureOffsetStepSize$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as double?)!; + } + } + + /// Sets the exposure offset of the camera with the given ID and returns the + /// actual exposure offset. + Future setExposureOffset(double offset) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setExposureOffset$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([offset]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as double?)!; + } + } + + /// Sets the focus mode of the camera with the given ID. + Future setFocusMode(PlatformFocusMode focusMode) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setFocusMode$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([focusMode]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the focus point of the camera with the given ID. + /// + /// A null value resets to the default focus point. + Future setFocusPoint(PlatformPoint? point) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setFocusPoint$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([point]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Returns the maximum zoom level of the camera with the given ID. + Future getMaxZoomLevel() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.getMaxZoomLevel$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as double?)!; + } + } + + /// Returns the minimum zoom level of the camera with the given ID. + Future getMinZoomLevel() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.getMinZoomLevel$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as double?)!; + } + } + + /// Sets the zoom level of the camera with the given ID. + Future setZoomLevel(double zoom) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setZoomLevel$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([zoom]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Pauses streaming of preview frames. + Future pausePreview() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.pausePreview$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Resumes previously paused streaming of preview frames. + Future resumePreview() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.resumePreview$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Changes the camera while recording video. + /// + /// This should be called only while video recording is active. + Future setDescriptionWhileRecording(String description) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.setDescriptionWhileRecording$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([description]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } } /// Handles calls from native side to Dart that are not camera-specific. diff --git a/packages/camera/camera_android/lib/src/utils.dart b/packages/camera/camera_android/lib/src/utils.dart index 0a8742fbf01..1b3d2809e2c 100644 --- a/packages/camera/camera_android/lib/src/utils.dart +++ b/packages/camera/camera_android/lib/src/utils.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 'dart:math'; + import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; @@ -9,13 +11,12 @@ import 'messages.g.dart'; /// Converts a [PlatformCameraLensDirection] to [CameraLensDirection]. CameraLensDirection cameraLensDirectionFromPlatform( - PlatformCameraLensDirection direction) { - return switch (direction) { - PlatformCameraLensDirection.front => CameraLensDirection.front, - PlatformCameraLensDirection.back => CameraLensDirection.back, - PlatformCameraLensDirection.external => CameraLensDirection.external, - }; -} + PlatformCameraLensDirection direction) => + switch (direction) { + PlatformCameraLensDirection.front => CameraLensDirection.front, + PlatformCameraLensDirection.back => CameraLensDirection.back, + PlatformCameraLensDirection.external => CameraLensDirection.external, + }; /// Converts a [PlatformDeviceOrientation] to [DeviceOrientation]. DeviceOrientation deviceOrientationFromPlatform( @@ -29,6 +30,25 @@ DeviceOrientation deviceOrientationFromPlatform( DeviceOrientation.landscapeRight, }; +/// Converts a [DeviceOrientation] to [PlatformDeviceOrientation]. +PlatformDeviceOrientation deviceOrientationToPlatform( + DeviceOrientation orientation) { + switch (orientation) { + case DeviceOrientation.portraitUp: + return PlatformDeviceOrientation.portraitUp; + case DeviceOrientation.portraitDown: + return PlatformDeviceOrientation.portraitDown; + case DeviceOrientation.landscapeLeft: + return PlatformDeviceOrientation.landscapeLeft; + case DeviceOrientation.landscapeRight: + return PlatformDeviceOrientation.landscapeRight; + } + // This enum is defined outside of this package. This fall-through case + // ensures that the code does not break if a new value is ever added. + // ignore: dead_code + return PlatformDeviceOrientation.portraitUp; +} + /// Converts a [PlatformExposureMode] to [ExposureMode]. ExposureMode exposureModeFromPlatform(PlatformExposureMode exposureMode) => switch (exposureMode) { @@ -36,6 +56,20 @@ ExposureMode exposureModeFromPlatform(PlatformExposureMode exposureMode) => PlatformExposureMode.locked => ExposureMode.locked, }; +/// Converts a [ExposureMode] to [PlatformExposureMode]. +PlatformExposureMode exposureModeToPlatform(ExposureMode exposureMode) { + switch (exposureMode) { + case ExposureMode.auto: + return PlatformExposureMode.auto; + case ExposureMode.locked: + return PlatformExposureMode.locked; + } + // This enum is defined outside of this package. This fall-through case + // ensures that the code does not break if a new value is ever added. + // ignore: dead_code + return PlatformExposureMode.auto; +} + /// Converts a [PlatformFocusMode] to [FocusMode]. FocusMode focusModeFromPlatform(PlatformFocusMode focusMode) => switch (focusMode) { @@ -43,23 +77,85 @@ FocusMode focusModeFromPlatform(PlatformFocusMode focusMode) => PlatformFocusMode.locked => FocusMode.locked, }; -/// Returns the device orientation as a String. -String serializeDeviceOrientation(DeviceOrientation orientation) { - switch (orientation) { - case DeviceOrientation.portraitUp: - return 'portraitUp'; - case DeviceOrientation.portraitDown: - return 'portraitDown'; - case DeviceOrientation.landscapeRight: - return 'landscapeRight'; - case DeviceOrientation.landscapeLeft: - return 'landscapeLeft'; +/// Converts a [FocusMode] to [PlatformFocusMode]. +PlatformFocusMode focusModeToPlatform(FocusMode focusMode) { + switch (focusMode) { + case FocusMode.auto: + return PlatformFocusMode.auto; + case FocusMode.locked: + return PlatformFocusMode.locked; + } + // This enum is defined outside of this package. This fall-through case + // ensures that the code does not break if a new value is ever added. + // ignore: dead_code + return PlatformFocusMode.auto; +} + +/// Converts a [ResolutionPreset] to [PlatformResolutionPreset]. +PlatformResolutionPreset resolutionPresetToPlatform(ResolutionPreset? preset) => + switch (preset) { + ResolutionPreset.low => PlatformResolutionPreset.low, + ResolutionPreset.medium => PlatformResolutionPreset.medium, + ResolutionPreset.high => PlatformResolutionPreset.high, + ResolutionPreset.veryHigh => PlatformResolutionPreset.veryHigh, + ResolutionPreset.ultraHigh => PlatformResolutionPreset.ultraHigh, + ResolutionPreset.max => PlatformResolutionPreset.max, + _ => PlatformResolutionPreset.high, + }; + +/// Converts a [MediaSettings] to [PlatformMediaSettings]. +PlatformMediaSettings mediaSettingsToPlatform(MediaSettings? settings) => + PlatformMediaSettings( + resolutionPreset: + resolutionPresetToPlatform(settings?.resolutionPreset), + enableAudio: settings?.enableAudio ?? false, + videoBitrate: settings?.videoBitrate, + audioBitrate: settings?.audioBitrate, + fps: settings?.fps); + +/// Converts an [ImageFormatGroup] to [PlatformImageFormatGroup]. +/// +/// [ImageFormatGroup.unknown] and [ImageFormatGroup.bgra8888] default to +/// [PlatformImageFormatGroup.yuv420], which is the default on Android. +PlatformImageFormatGroup imageFormatGroupToPlatform(ImageFormatGroup format) { + switch (format) { + case ImageFormatGroup.unknown: + return PlatformImageFormatGroup.yuv420; + case ImageFormatGroup.yuv420: + return PlatformImageFormatGroup.yuv420; + case ImageFormatGroup.bgra8888: + return PlatformImageFormatGroup.yuv420; + case ImageFormatGroup.jpeg: + return PlatformImageFormatGroup.jpeg; + case ImageFormatGroup.nv21: + return PlatformImageFormatGroup.nv21; + } + // This enum is defined outside of this package. This fall-through case + // ensures that the code does not break if a new value is ever added. + // ignore: dead_code + return PlatformImageFormatGroup.yuv420; +} + +/// Converts a [FlashMode] to [PlatformFlashMode]. +PlatformFlashMode flashModeToPlatform(FlashMode mode) { + switch (mode) { + case FlashMode.auto: + return PlatformFlashMode.auto; + case FlashMode.off: + return PlatformFlashMode.off; + case FlashMode.always: + return PlatformFlashMode.always; + case FlashMode.torch: + return PlatformFlashMode.torch; } - // The enum comes from a different package, which could get a new value at - // any time, so provide a fallback that ensures this won't break when used - // with a version that contains new values. This is deliberately outside - // the switch rather than a `default` so that the linter will flag the - // switch as needing an update. + // This enum is defined outside of this package. This fall-through case + // ensures that the code does not break if a new value is ever added. // ignore: dead_code - return 'portraitUp'; + return PlatformFlashMode.auto; } + +/// Converts a [Point] to [PlatformPoint]. +/// +/// Null becomes null. +PlatformPoint? pointToPlatform(Point? point) => + (point != null) ? PlatformPoint(x: point.x, y: point.y) : null; diff --git a/packages/camera/camera_android/pigeons/messages.dart b/packages/camera/camera_android/pigeons/messages.dart index 5e0e6383c32..6031ac3d5a3 100644 --- a/packages/camera/camera_android/pigeons/messages.dart +++ b/packages/camera/camera_android/pigeons/messages.dart @@ -23,6 +23,7 @@ class PlatformCameraDescription { {required this.name, required this.lensDirection, required this.sensorOrientation}); + final String name; final PlatformCameraLensDirection lensDirection; final int sensorOrientation; @@ -56,6 +57,7 @@ class PlatformCameraState { required this.focusMode, required this.exposurePointSupported, required this.focusPointSupported}); + final PlatformSize previewSize; final PlatformExposureMode exposureMode; final PlatformFocusMode focusMode; @@ -66,15 +68,163 @@ class PlatformCameraState { /// Pigeon equivalent of [Size]. class PlatformSize { PlatformSize({required this.width, required this.height}); + final double width; final double height; } +/// Pigeon equivalent of [Point]. +class PlatformPoint { + PlatformPoint({required this.x, required this.y}); + + final double x; + final double y; +} + +/// Pigeon equivalent of [ResolutionPreset]. +enum PlatformResolutionPreset { + low, + medium, + high, + veryHigh, + ultraHigh, + max, +} + +/// Pigeon equivalent of [MediaSettings]. +class PlatformMediaSettings { + PlatformMediaSettings( + {required this.resolutionPreset, + required this.enableAudio, + this.fps, + this.videoBitrate, + this.audioBitrate}); + final PlatformResolutionPreset resolutionPreset; + final int? fps; + final int? videoBitrate; + final int? audioBitrate; + final bool enableAudio; +} + +/// Pigeon equivalent of [ImageFormatGroup]. +enum PlatformImageFormatGroup { + /// The default for Android. + yuv420, + jpeg, + nv21, +} + +/// Pigeon equivalent of [FlashMode]. +enum PlatformFlashMode { + off, + auto, + always, + torch, +} + /// Handles calls from Dart to the native side. @HostApi() abstract class CameraApi { /// Returns the list of available cameras. List getAvailableCameras(); + + /// Creates a new camera with the given name and settings and returns its ID. + @async + int create(String cameraName, PlatformMediaSettings mediaSettings); + + /// Initializes the camera with the given ID for the given image format. + void initialize(PlatformImageFormatGroup imageFormat); + + /// Disposes of the camera with the given ID. + void dispose(); + + /// Locks the camera with the given ID to the given orientation. + void lockCaptureOrientation(PlatformDeviceOrientation orientation); + + /// Unlocks the orientation for the camera with the given ID. + void unlockCaptureOrientation(); + + /// Takes a picture on the camera with the given ID and returns a path to the + /// resulting file. + @async + String takePicture(); + + /// Starts recording a video on the camera with the given ID. + void startVideoRecording(bool enableStream); + + /// Ends video recording on the camera with the given ID and returns the path + /// to the resulting file. + String stopVideoRecording(); + + /// Pauses video recording on the camera with the given ID. + void pauseVideoRecording(); + + /// Resumes previously paused video recording on the camera with the given ID. + void resumeVideoRecording(); + + /// Begins streaming frames from the camera. + void startImageStream(); + + /// Stops streaming frames from the camera. + void stopImageStream(); + + /// Sets the flash mode of the camera with the given ID. + @async + void setFlashMode(PlatformFlashMode flashMode); + + /// Sets the exposure mode of the camera with the given ID. + @async + void setExposureMode(PlatformExposureMode exposureMode); + + /// Sets the exposure point of the camera with the given ID. + /// + /// A null value resets to the default exposure point. + @async + void setExposurePoint(PlatformPoint? point); + + /// Returns the minimum exposure offset of the camera with the given ID. + double getMinExposureOffset(); + + /// Returns the maximum exposure offset of the camera with the given ID. + double getMaxExposureOffset(); + + /// Returns the exposure step size of the camera with the given ID. + double getExposureOffsetStepSize(); + + /// Sets the exposure offset of the camera with the given ID and returns the + /// actual exposure offset. + @async + double setExposureOffset(double offset); + + /// Sets the focus mode of the camera with the given ID. + void setFocusMode(PlatformFocusMode focusMode); + + /// Sets the focus point of the camera with the given ID. + /// + /// A null value resets to the default focus point. + @async + void setFocusPoint(PlatformPoint? point); + + /// Returns the maximum zoom level of the camera with the given ID. + double getMaxZoomLevel(); + + /// Returns the minimum zoom level of the camera with the given ID. + double getMinZoomLevel(); + + /// Sets the zoom level of the camera with the given ID. + @async + void setZoomLevel(double zoom); + + /// Pauses streaming of preview frames. + void pausePreview(); + + /// Resumes previously paused streaming of preview frames. + void resumePreview(); + + /// Changes the camera while recording video. + /// + /// This should be called only while video recording is active. + void setDescriptionWhileRecording(String description); } /// Handles calls from native side to Dart that are not camera-specific. diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 7c3cb83a46a..d666175eaa5 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.9+14 +version: 0.10.9+15 environment: sdk: ^3.5.0 diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index 4024992ec57..7c6d55c485b 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -17,11 +17,8 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'android_camera_test.mocks.dart'; -import 'method_channel_mock.dart'; -const String _channelName = 'plugins.flutter.io/camera_android'; - -@GenerateMocks([CameraApi]) +@GenerateNiceMocks(>[MockSpec()]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -49,17 +46,19 @@ void main() { }); group('Creation, Initialization & Disposal Tests', () { + late MockCameraApi mockCameraApi; + setUp(() { + mockCameraApi = MockCameraApi(); + }); + test('Should send creation data and receive back a camera id', () async { // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: _channelName, - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - } - }); - final AndroidCamera camera = AndroidCamera(); + final AndroidCamera camera = AndroidCamera(hostApi: mockCameraApi); + when(mockCameraApi.create( + 'Test', + argThat(predicate((PlatformMediaSettings settings) => + settings.resolutionPreset == PlatformResolutionPreset.high && + !settings.enableAudio)))).thenAnswer((_) async => 1); // Act final int cameraId = await camera.createCamera( @@ -71,19 +70,6 @@ void main() { ); // Assert - expect(cameraMockChannel.log, [ - isMethodCall( - 'create', - arguments: { - 'cameraName': 'Test', - 'resolutionPreset': 'high', - 'enableAudio': false, - 'fps': null, - 'videoBitrate': null, - 'audioBitrate': null, - }, - ), - ]); expect(cameraId, 1); }); @@ -91,15 +77,15 @@ void main() { 'Should send creation data and receive back a camera id using createCameraWithSettings', () async { // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: _channelName, - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - } - }); - final AndroidCamera camera = AndroidCamera(); + final AndroidCamera camera = AndroidCamera(hostApi: mockCameraApi); + when(mockCameraApi.create( + 'Test', + argThat(predicate((PlatformMediaSettings settings) => + settings.resolutionPreset == PlatformResolutionPreset.low && + !settings.enableAudio && + settings.fps == 15 && + settings.videoBitrate == 200000 && + settings.audioBitrate == 32000)))).thenAnswer((_) async => 1); // Act final int cameraId = await camera.createCameraWithSettings( @@ -116,63 +102,19 @@ void main() { ); // Assert - expect(cameraMockChannel.log, [ - isMethodCall( - 'create', - arguments: { - 'cameraName': 'Test', - 'resolutionPreset': 'low', - 'fps': 15, - 'videoBitrate': 200000, - 'audioBitrate': 32000, - 'enableAudio': false - }, - ), - ]); expect(cameraId, 1); }); test('Should throw CameraException when create throws a PlatformException', () { // Arrange - MethodChannelMock(channelName: _channelName, methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); - final AndroidCamera camera = AndroidCamera(); - - // Act - expect( - () => camera.createCamera( - const CameraDescription( - name: 'Test', - lensDirection: CameraLensDirection.back, - sensorOrientation: 0, - ), - ResolutionPreset.high, - ), - throwsA( - isA() - .having( - (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') - .having((CameraException e) => e.description, 'description', - 'Mock error message used during testing.'), - ), - ); - }); - - test('Should throw CameraException when create throws a PlatformException', - () { - // Arrange - MethodChannelMock(channelName: _channelName, methods: { - 'create': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); - final AndroidCamera camera = AndroidCamera(); + final AndroidCamera camera = AndroidCamera(hostApi: mockCameraApi); + when(mockCameraApi.create( + 'Test', + argThat(predicate((PlatformMediaSettings settings) => + settings.resolutionPreset == PlatformResolutionPreset.high && + !settings.enableAudio)))).thenThrow(CameraException( + 'TESTING_ERROR_CODE', 'Mock error message used during testing.')); // Act expect( @@ -198,16 +140,10 @@ void main() { 'Should throw CameraException when initialize throws a PlatformException', () { // Arrange - MethodChannelMock( - channelName: _channelName, - methods: { - 'initialize': PlatformException( - code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }, - ); - final AndroidCamera camera = AndroidCamera(); + final AndroidCamera camera = AndroidCamera(hostApi: mockCameraApi); + when(mockCameraApi.initialize(PlatformImageFormatGroup.yuv420)) + .thenThrow(CameraException('TESTING_ERROR_CODE', + 'Mock error message used during testing.')); // Act expect( @@ -228,16 +164,13 @@ void main() { test('Should send initialization data', () async { // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: _channelName, - methods: { - 'create': { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - }, - 'initialize': null - }); - final AndroidCamera camera = AndroidCamera(); + final AndroidCamera camera = AndroidCamera(hostApi: mockCameraApi); + when(mockCameraApi.create( + 'Test', + argThat(predicate((PlatformMediaSettings settings) => + settings.resolutionPreset == PlatformResolutionPreset.high && + !settings.enableAudio)))).thenAnswer((_) async => 1); + final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -262,29 +195,18 @@ void main() { // Assert expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - isMethodCall( - 'initialize', - arguments: { - 'cameraId': 1, - 'imageFormatGroup': 'unknown', - }, - ), - ]); + verify(mockCameraApi.initialize(PlatformImageFormatGroup.yuv420)) + .called(1); }); test('Should send a disposal call on dispose', () async { // Arrange - final MethodChannelMock cameraMockChannel = MethodChannelMock( - channelName: _channelName, - methods: { - 'create': {'cameraId': 1}, - 'initialize': null, - 'dispose': {'cameraId': 1} - }); - - final AndroidCamera camera = AndroidCamera(); + final AndroidCamera camera = AndroidCamera(hostApi: mockCameraApi); + when(mockCameraApi.create( + 'Test', + argThat(predicate((PlatformMediaSettings settings) => + settings.resolutionPreset == PlatformResolutionPreset.high && + !settings.enableAudio)))).thenAnswer((_) async => 1); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -310,29 +232,17 @@ void main() { // Assert expect(cameraId, 1); - expect(cameraMockChannel.log, [ - anything, - anything, - isMethodCall( - 'dispose', - arguments: {'cameraId': 1}, - ), - ]); + verify(mockCameraApi.dispose()).called(1); }); }); group('Event Tests', () { late AndroidCamera camera; late int cameraId; + late MockCameraApi mockCameraApi; setUp(() async { - MethodChannelMock( - channelName: _channelName, - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); - camera = AndroidCamera(); + mockCameraApi = MockCameraApi(); + camera = AndroidCamera(hostApi: mockCameraApi); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -462,13 +372,6 @@ void main() { late MockCameraApi mockCameraApi; setUp(() async { - MethodChannelMock( - channelName: _channelName, - methods: { - 'create': {'cameraId': 1}, - 'initialize': null - }, - ); mockCameraApi = MockCameraApi(); camera = AndroidCamera(hostApi: mockCameraApi); cameraId = await camera.createCamera( @@ -551,66 +454,29 @@ void main() { test('Should take a picture and return an XFile instance', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'takePicture': '/test/path.jpg'}); + when(mockCameraApi.takePicture()) + .thenAnswer((_) async => '/test/path.jpg'); // Act final XFile file = await camera.takePicture(cameraId); // Assert - expect(channel.log, [ - isMethodCall('takePicture', arguments: { - 'cameraId': cameraId, - }), - ]); expect(file.path, '/test/path.jpg'); }); - test('Should prepare for video recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'prepareForVideoRecording': null}, - ); - - // Act - await camera.prepareForVideoRecording(); - - // Assert - expect(channel.log, [ - isMethodCall('prepareForVideoRecording', arguments: null), - ]); - }); - test('Should start recording a video', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'startVideoRecording': null}, - ); - // Act await camera.startVideoRecording(cameraId); // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'enableStream': false, - }), - ]); + verify(mockCameraApi.startVideoRecording(false)).called(1); }); test( 'Should pass enableStream if callback is passed when starting recording a video', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'startVideoRecording': null}, - ); - // Act await camera.startVideoCapturing( VideoCaptureOptions(cameraId, @@ -618,75 +484,41 @@ void main() { ); // Assert - expect(channel.log, [ - isMethodCall('startVideoRecording', arguments: { - 'cameraId': cameraId, - 'enableStream': true, - }), - ]); + verify(mockCameraApi.startVideoRecording(true)).called(1); }); test('Should stop a video recording and return the file', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'stopVideoRecording': '/test/path.mp4'}, - ); + when(mockCameraApi.stopVideoRecording()) + .thenAnswer((_) async => '/test/path.mp4'); // Act final XFile file = await camera.stopVideoRecording(cameraId); // Assert - expect(channel.log, [ - isMethodCall('stopVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); expect(file.path, '/test/path.mp4'); }); test('Should pause a video recording', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'pauseVideoRecording': null}, - ); - // Act await camera.pauseVideoRecording(cameraId); // Assert - expect(channel.log, [ - isMethodCall('pauseVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); + verify(mockCameraApi.pauseVideoRecording()).called(1); }); test('Should resume a video recording', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'resumeVideoRecording': null}, - ); - // Act await camera.resumeVideoRecording(cameraId); // Assert - expect(channel.log, [ - isMethodCall('resumeVideoRecording', arguments: { - 'cameraId': cameraId, - }), - ]); + verify(mockCameraApi.resumeVideoRecording()).called(1); }); test('Should set the description while recording', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setDescriptionWhileRecording': null}, - ); const CameraDescription camera2Description = CameraDescription( name: 'Test2', lensDirection: CameraLensDirection.front, @@ -696,21 +528,13 @@ void main() { await camera.setDescriptionWhileRecording(camera2Description); // Assert - expect(channel.log, [ - isMethodCall('setDescriptionWhileRecording', - arguments: { - 'cameraName': camera2Description.name, - }), - ]); + verify(mockCameraApi + .setDescriptionWhileRecording(camera2Description.name)) + .called(1); }); test('Should set the flash mode', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setFlashMode': null}, - ); - // Act await camera.setFlashMode(cameraId, FlashMode.torch); await camera.setFlashMode(cameraId, FlashMode.always); @@ -718,78 +542,41 @@ void main() { await camera.setFlashMode(cameraId, FlashMode.off); // Assert - expect(channel.log, [ - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'torch' - }), - isMethodCall('setFlashMode', arguments: { - 'cameraId': cameraId, - 'mode': 'always' - }), - isMethodCall('setFlashMode', - arguments: {'cameraId': cameraId, 'mode': 'auto'}), - isMethodCall('setFlashMode', - arguments: {'cameraId': cameraId, 'mode': 'off'}), - ]); + verify(mockCameraApi.setFlashMode(PlatformFlashMode.torch)).called(1); + verify(mockCameraApi.setFlashMode(PlatformFlashMode.always)).called(1); + verify(mockCameraApi.setFlashMode(PlatformFlashMode.auto)).called(1); + verify(mockCameraApi.setFlashMode(PlatformFlashMode.off)).called(1); }); test('Should set the exposure mode', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - 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' - }), - ]); + verify(mockCameraApi.setExposureMode(PlatformExposureMode.auto)) + .called(1); + verify(mockCameraApi.setExposureMode(PlatformExposureMode.locked)) + .called(1); }); test('Should set the exposure point', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setExposurePoint': null}, - ); - // Act - await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); + await camera.setExposurePoint(cameraId, const Point(0.4, 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 - }), - ]); + verify(mockCameraApi.setExposurePoint(argThat(predicate( + (PlatformPoint point) => point.x == 0.4 && point.y == 0.5)))) + .called(1); + verify(mockCameraApi.setExposurePoint(null)).called(1); }); test('Should get the min exposure offset', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getMinExposureOffset': 2.0}, - ); + when(mockCameraApi.getMinExposureOffset()).thenAnswer((_) async => 2.0); // Act final double minExposureOffset = @@ -797,19 +584,11 @@ void main() { // Assert expect(minExposureOffset, 2.0); - expect(channel.log, [ - isMethodCall('getMinExposureOffset', arguments: { - 'cameraId': cameraId, - }), - ]); }); test('Should get the max exposure offset', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getMaxExposureOffset': 2.0}, - ); + when(mockCameraApi.getMaxExposureOffset()).thenAnswer((_) async => 2.0); // Act final double maxExposureOffset = @@ -817,100 +596,40 @@ void main() { // Assert expect(maxExposureOffset, 2.0); - expect(channel.log, [ - isMethodCall('getMaxExposureOffset', arguments: { - 'cameraId': cameraId, - }), - ]); }); test('Should get the exposure offset step size', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getExposureOffsetStepSize': 0.25}, - ); + when(mockCameraApi.getExposureOffsetStepSize()) + .thenAnswer((_) async => 0.25); // Act final double 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 - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setExposureOffset': 0.6}, - ); + when(mockCameraApi.setExposureOffset(0.5)).thenAnswer((_) async => 0.6); // Act final double 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 set the focus mode', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - 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 - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setFocusPoint': null}, - ); - - // Act - await camera.setFocusPoint(cameraId, const 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 - }), - ]); + verify(mockCameraApi.setFocusMode(PlatformFocusMode.auto)).called(1); + verify(mockCameraApi.setFocusMode(PlatformFocusMode.locked)).called(1); }); test('Should build a texture widget as preview widget', () async { @@ -924,71 +643,40 @@ void main() { test('Should get the max zoom level', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getMaxZoomLevel': 10.0}, - ); + when(mockCameraApi.getMaxZoomLevel()).thenAnswer((_) async => 10.0); // Act final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); // Assert expect(maxZoomLevel, 10.0); - expect(channel.log, [ - isMethodCall('getMaxZoomLevel', arguments: { - 'cameraId': cameraId, - }), - ]); }); test('Should get the min zoom level', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'getMinZoomLevel': 1.0}, - ); + when(mockCameraApi.getMinZoomLevel()).thenAnswer((_) async => 1.0); // Act final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); // Assert expect(maxZoomLevel, 1.0); - expect(channel.log, [ - isMethodCall('getMinZoomLevel', arguments: { - 'cameraId': cameraId, - }), - ]); }); test('Should set the zoom level', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setZoomLevel': null}, - ); - // Act await camera.setZoomLevel(cameraId, 2.0); // Assert - expect(channel.log, [ - isMethodCall('setZoomLevel', - arguments: {'cameraId': cameraId, 'zoom': 2.0}), - ]); + verify(mockCameraApi.setZoomLevel(2.0)).called(1); }); test('Should throw CameraException when illegal zoom level is supplied', () async { // Arrange - MethodChannelMock( - channelName: _channelName, - methods: { - 'setZoomLevel': PlatformException( - code: 'ZOOM_ERROR', - message: 'Illegal zoom error', - ) - }, - ); + when(mockCameraApi.setZoomLevel(-1.0)).thenThrow( + PlatformException(code: 'ZOOM_ERROR', message: 'Illegal zoom error')); // Act & assert expect( @@ -1001,108 +689,58 @@ void main() { test('Should lock the capture orientation', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'lockCaptureOrientation': null}, - ); - // Act await camera.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp); // Assert - expect(channel.log, [ - isMethodCall('lockCaptureOrientation', arguments: { - 'cameraId': cameraId, - 'orientation': 'portraitUp' - }), - ]); + verify(mockCameraApi + .lockCaptureOrientation(PlatformDeviceOrientation.portraitUp)) + .called(1); }); test('Should unlock the capture orientation', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'unlockCaptureOrientation': null}, - ); - // Act await camera.unlockCaptureOrientation(cameraId); // Assert - expect(channel.log, [ - isMethodCall('unlockCaptureOrientation', - arguments: {'cameraId': cameraId}), - ]); + verify(mockCameraApi.unlockCaptureOrientation()).called(1); }); test('Should pause the camera preview', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'pausePreview': null}, - ); - // Act await camera.pausePreview(cameraId); // Assert - expect(channel.log, [ - isMethodCall('pausePreview', - arguments: {'cameraId': cameraId}), - ]); + verify(mockCameraApi.pausePreview()).called(1); }); test('Should resume the camera preview', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'resumePreview': null}, - ); - // Act await camera.resumePreview(cameraId); // Assert - expect(channel.log, [ - isMethodCall('resumePreview', - arguments: {'cameraId': cameraId}), - ]); + verify(mockCameraApi.resumePreview()).called(1); }); test('Should start streaming', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: { - 'startImageStream': null, - 'stopImageStream': null, - }, - ); - // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); // Assert - expect(channel.log, [ - isMethodCall('startImageStream', arguments: null), - ]); + verify(mockCameraApi.startImageStream()).called(1); await subscription.cancel(); }); test('Should stop streaming', () async { // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: { - 'startImageStream': null, - 'stopImageStream': null, - }, - ); - // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) @@ -1110,10 +748,8 @@ void main() { await subscription.cancel(); // Assert - expect(channel.log, [ - isMethodCall('startImageStream', arguments: null), - isMethodCall('stopImageStream', arguments: null), - ]); + verify(mockCameraApi.startImageStream()).called(1); + verify(mockCameraApi.stopImageStream()).called(1); }); }); } diff --git a/packages/camera/camera_android/test/android_camera_test.mocks.dart b/packages/camera/camera_android/test/android_camera_test.mocks.dart index c833a265e36..6be1c98ad57 100644 --- a/packages/camera/camera_android/test/android_camera_test.mocks.dart +++ b/packages/camera/camera_android/test/android_camera_test.mocks.dart @@ -26,10 +26,6 @@ import 'package:mockito/src/dummies.dart' as _i3; /// /// See the documentation for Mockito's code generation for more information. class MockCameraApi extends _i1.Mock implements _i2.CameraApi { - MockCameraApi() { - _i1.throwOnMissingStub(this); - } - @override String get pigeonVar_messageChannelSuffix => (super.noSuchMethod( Invocation.getter(#pigeonVar_messageChannelSuffix), @@ -37,6 +33,10 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi { this, Invocation.getter(#pigeonVar_messageChannelSuffix), ), + returnValueForMissingStub: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), ) as String); @override @@ -48,5 +48,321 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi { ), returnValue: _i4.Future>.value( <_i2.PlatformCameraDescription>[]), + returnValueForMissingStub: + _i4.Future>.value( + <_i2.PlatformCameraDescription>[]), ) as _i4.Future>); + + @override + _i4.Future create( + String? cameraName, + _i2.PlatformMediaSettings? mediaSettings, + ) => + (super.noSuchMethod( + Invocation.method( + #create, + [ + cameraName, + mediaSettings, + ], + ), + returnValue: _i4.Future.value(0), + returnValueForMissingStub: _i4.Future.value(0), + ) as _i4.Future); + + @override + _i4.Future initialize(_i2.PlatformImageFormatGroup? imageFormat) => + (super.noSuchMethod( + Invocation.method( + #initialize, + [imageFormat], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future dispose() => (super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future lockCaptureOrientation( + _i2.PlatformDeviceOrientation? orientation) => + (super.noSuchMethod( + Invocation.method( + #lockCaptureOrientation, + [orientation], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future unlockCaptureOrientation() => (super.noSuchMethod( + Invocation.method( + #unlockCaptureOrientation, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future takePicture() => (super.noSuchMethod( + Invocation.method( + #takePicture, + [], + ), + returnValue: _i4.Future.value(_i3.dummyValue( + this, + Invocation.method( + #takePicture, + [], + ), + )), + returnValueForMissingStub: + _i4.Future.value(_i3.dummyValue( + this, + Invocation.method( + #takePicture, + [], + ), + )), + ) as _i4.Future); + + @override + _i4.Future startVideoRecording(bool? enableStream) => + (super.noSuchMethod( + Invocation.method( + #startVideoRecording, + [enableStream], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future stopVideoRecording() => (super.noSuchMethod( + Invocation.method( + #stopVideoRecording, + [], + ), + returnValue: _i4.Future.value(_i3.dummyValue( + this, + Invocation.method( + #stopVideoRecording, + [], + ), + )), + returnValueForMissingStub: + _i4.Future.value(_i3.dummyValue( + this, + Invocation.method( + #stopVideoRecording, + [], + ), + )), + ) as _i4.Future); + + @override + _i4.Future pauseVideoRecording() => (super.noSuchMethod( + Invocation.method( + #pauseVideoRecording, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future resumeVideoRecording() => (super.noSuchMethod( + Invocation.method( + #resumeVideoRecording, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future startImageStream() => (super.noSuchMethod( + Invocation.method( + #startImageStream, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future stopImageStream() => (super.noSuchMethod( + Invocation.method( + #stopImageStream, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setFlashMode(_i2.PlatformFlashMode? flashMode) => + (super.noSuchMethod( + Invocation.method( + #setFlashMode, + [flashMode], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setExposureMode(_i2.PlatformExposureMode? exposureMode) => + (super.noSuchMethod( + Invocation.method( + #setExposureMode, + [exposureMode], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setExposurePoint(_i2.PlatformPoint? point) => + (super.noSuchMethod( + Invocation.method( + #setExposurePoint, + [point], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future getMinExposureOffset() => (super.noSuchMethod( + Invocation.method( + #getMinExposureOffset, + [], + ), + returnValue: _i4.Future.value(0.0), + returnValueForMissingStub: _i4.Future.value(0.0), + ) as _i4.Future); + + @override + _i4.Future getMaxExposureOffset() => (super.noSuchMethod( + Invocation.method( + #getMaxExposureOffset, + [], + ), + returnValue: _i4.Future.value(0.0), + returnValueForMissingStub: _i4.Future.value(0.0), + ) as _i4.Future); + + @override + _i4.Future getExposureOffsetStepSize() => (super.noSuchMethod( + Invocation.method( + #getExposureOffsetStepSize, + [], + ), + returnValue: _i4.Future.value(0.0), + returnValueForMissingStub: _i4.Future.value(0.0), + ) as _i4.Future); + + @override + _i4.Future setExposureOffset(double? offset) => (super.noSuchMethod( + Invocation.method( + #setExposureOffset, + [offset], + ), + returnValue: _i4.Future.value(0.0), + returnValueForMissingStub: _i4.Future.value(0.0), + ) as _i4.Future); + + @override + _i4.Future setFocusMode(_i2.PlatformFocusMode? focusMode) => + (super.noSuchMethod( + Invocation.method( + #setFocusMode, + [focusMode], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setFocusPoint(_i2.PlatformPoint? point) => + (super.noSuchMethod( + Invocation.method( + #setFocusPoint, + [point], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future getMaxZoomLevel() => (super.noSuchMethod( + Invocation.method( + #getMaxZoomLevel, + [], + ), + returnValue: _i4.Future.value(0.0), + returnValueForMissingStub: _i4.Future.value(0.0), + ) as _i4.Future); + + @override + _i4.Future getMinZoomLevel() => (super.noSuchMethod( + Invocation.method( + #getMinZoomLevel, + [], + ), + returnValue: _i4.Future.value(0.0), + returnValueForMissingStub: _i4.Future.value(0.0), + ) as _i4.Future); + + @override + _i4.Future setZoomLevel(double? zoom) => (super.noSuchMethod( + Invocation.method( + #setZoomLevel, + [zoom], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future pausePreview() => (super.noSuchMethod( + Invocation.method( + #pausePreview, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future resumePreview() => (super.noSuchMethod( + Invocation.method( + #resumePreview, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setDescriptionWhileRecording(String? description) => + (super.noSuchMethod( + Invocation.method( + #setDescriptionWhileRecording, + [description], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); } diff --git a/packages/camera/camera_android/test/utils_test.dart b/packages/camera/camera_android/test/utils_test.dart index 864b83d7e1d..ebcac58d2c6 100644 --- a/packages/camera/camera_android/test/utils_test.dart +++ b/packages/camera/camera_android/test/utils_test.dart @@ -27,17 +27,6 @@ void main() { ); }); - test('serializeDeviceOrientation() should serialize correctly', () { - expect(serializeDeviceOrientation(DeviceOrientation.portraitUp), - 'portraitUp'); - expect(serializeDeviceOrientation(DeviceOrientation.portraitDown), - 'portraitDown'); - expect(serializeDeviceOrientation(DeviceOrientation.landscapeRight), - 'landscapeRight'); - expect(serializeDeviceOrientation(DeviceOrientation.landscapeLeft), - 'landscapeLeft'); - }); - test('deviceOrientationFromPlatform() should convert correctly', () { expect( deviceOrientationFromPlatform(PlatformDeviceOrientation.portraitUp),