diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 3b8bf9ac7b3b..3ffef761e2fa 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.9+13 + +* Converts `getAvailableCameras` to Pigeon. + ## 0.10.9+12 * Updates Java compatibility version to 11. 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 d98984cbf2f5..c9eedaa3b797 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 @@ -13,9 +13,7 @@ import androidx.annotation.NonNull; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** Provides various utilities for camera. */ public final class CameraUtils { @@ -85,6 +83,27 @@ static PlatformChannel.DeviceOrientation deserializeDeviceOrientation(String ori } } + /** + * Converts a raw integer to a PlatformCameraLensDirection enum. + * + * @param lensDirection One of CameraMetadata.LENS_FACING_FRONT, LENS_FACING_BACK, or + * LENS_FACING_EXTERNAL. + * @return One of Messages.PlatformCameraLensDirection.FRONT, BACK, or EXTERNAL. + */ + static Messages.PlatformCameraLensDirection lensDirectionFromInteger(int lensDirection) { + switch (lensDirection) { + case CameraMetadata.LENS_FACING_FRONT: + return Messages.PlatformCameraLensDirection.FRONT; + case CameraMetadata.LENS_FACING_BACK: + return Messages.PlatformCameraLensDirection.BACK; + case CameraMetadata.LENS_FACING_EXTERNAL: + return Messages.PlatformCameraLensDirection.EXTERNAL; + } + // CameraMetadata is defined in the Android API. In the event that a new value is added, a + // default fallback value of FRONT is returned. + return Messages.PlatformCameraLensDirection.FRONT; + } + /** * Gets all the available cameras for the device. * @@ -93,11 +112,11 @@ static PlatformChannel.DeviceOrientation deserializeDeviceOrientation(String ori * @throws CameraAccessException when the camera could not be accessed. */ @NonNull - public static List> getAvailableCameras(@NonNull Activity activity) - throws CameraAccessException { + public static List getAvailableCameras( + @NonNull Activity activity) throws CameraAccessException { CameraManager cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); String[] cameraNames = cameraManager.getCameraIdList(); - List> cameras = new ArrayList<>(); + List cameras = new ArrayList<>(); for (String cameraName : cameraNames) { int cameraId; try { @@ -109,24 +128,17 @@ public static List> getAvailableCameras(@NonNull Activity ac continue; } - HashMap details = new HashMap<>(); CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); - details.put("name", cameraName); int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - details.put("sensorOrientation", sensorOrientation); int lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING); - switch (lensFacing) { - case CameraMetadata.LENS_FACING_FRONT: - details.put("lensFacing", "front"); - break; - case CameraMetadata.LENS_FACING_BACK: - details.put("lensFacing", "back"); - break; - case CameraMetadata.LENS_FACING_EXTERNAL: - details.put("lensFacing", "external"); - break; - } + Messages.PlatformCameraLensDirection lensDirection = lensDirectionFromInteger(lensFacing); + Messages.PlatformCameraDescription details = + new Messages.PlatformCameraDescription.Builder() + .setName(cameraName) + .setSensorOrientation((long) sensorOrientation) + .setLensDirection(lensDirection) + .build(); cameras.add(details); } return cameras; 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 new file mode 100644 index 000000000000..2944bcd65ebf --- /dev/null +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Messages.java @@ -0,0 +1,280 @@ +// 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. +// Autogenerated from Pigeon (v22.4.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package io.flutter.plugins.camera; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** Generated class from Pigeon. */ +@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) +public class Messages { + + /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ + public static class FlutterError extends RuntimeException { + + /** The error code. */ + public final String code; + + /** The error details. Must be a datatype supported by the api codec. */ + public final Object details; + + public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) { + super(message); + this.code = code; + this.details = details; + } + } + + @NonNull + protected static ArrayList wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList<>(3); + if (exception instanceof FlutterError) { + FlutterError error = (FlutterError) exception; + errorList.add(error.code); + errorList.add(error.getMessage()); + errorList.add(error.details); + } else { + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + } + return errorList; + } + + @Target(METHOD) + @Retention(CLASS) + @interface CanIgnoreReturnValue {} + + public enum PlatformCameraLensDirection { + FRONT(0), + BACK(1), + EXTERNAL(2); + + final int index; + + PlatformCameraLensDirection(final int index) { + this.index = index; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class PlatformCameraDescription { + private @NonNull String name; + + public @NonNull String getName() { + return name; + } + + public void setName(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"name\" is null."); + } + this.name = setterArg; + } + + private @NonNull PlatformCameraLensDirection lensDirection; + + public @NonNull PlatformCameraLensDirection getLensDirection() { + return lensDirection; + } + + public void setLensDirection(@NonNull PlatformCameraLensDirection setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"lensDirection\" is null."); + } + this.lensDirection = setterArg; + } + + private @NonNull Long sensorOrientation; + + public @NonNull Long getSensorOrientation() { + return sensorOrientation; + } + + public void setSensorOrientation(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"sensorOrientation\" is null."); + } + this.sensorOrientation = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformCameraDescription() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformCameraDescription that = (PlatformCameraDescription) o; + return name.equals(that.name) + && lensDirection.equals(that.lensDirection) + && sensorOrientation.equals(that.sensorOrientation); + } + + @Override + public int hashCode() { + return Objects.hash(name, lensDirection, sensorOrientation); + } + + public static final class Builder { + + private @Nullable String name; + + @CanIgnoreReturnValue + public @NonNull Builder setName(@NonNull String setterArg) { + this.name = setterArg; + return this; + } + + private @Nullable PlatformCameraLensDirection lensDirection; + + @CanIgnoreReturnValue + public @NonNull Builder setLensDirection(@NonNull PlatformCameraLensDirection setterArg) { + this.lensDirection = setterArg; + return this; + } + + private @Nullable Long sensorOrientation; + + @CanIgnoreReturnValue + public @NonNull Builder setSensorOrientation(@NonNull Long setterArg) { + this.sensorOrientation = setterArg; + return this; + } + + public @NonNull PlatformCameraDescription build() { + PlatformCameraDescription pigeonReturn = new PlatformCameraDescription(); + pigeonReturn.setName(name); + pigeonReturn.setLensDirection(lensDirection); + pigeonReturn.setSensorOrientation(sensorOrientation); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(3); + toListResult.add(name); + toListResult.add(lensDirection); + toListResult.add(sensorOrientation); + return toListResult; + } + + static @NonNull PlatformCameraDescription fromList(@NonNull ArrayList pigeonVar_list) { + PlatformCameraDescription pigeonResult = new PlatformCameraDescription(); + Object name = pigeonVar_list.get(0); + pigeonResult.setName((String) name); + Object lensDirection = pigeonVar_list.get(1); + pigeonResult.setLensDirection((PlatformCameraLensDirection) lensDirection); + Object sensorOrientation = pigeonVar_list.get(2); + pigeonResult.setSensorOrientation((Long) sensorOrientation); + return pigeonResult; + } + } + + private static class PigeonCodec extends StandardMessageCodec { + public static final PigeonCodec INSTANCE = new PigeonCodec(); + + private PigeonCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 129: + { + Object value = readValue(buffer); + return value == null + ? null + : PlatformCameraLensDirection.values()[((Long) value).intValue()]; + } + case (byte) 130: + return PlatformCameraDescription.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof PlatformCameraLensDirection) { + stream.write(129); + writeValue(stream, value == null ? null : ((PlatformCameraLensDirection) value).index); + } else if (value instanceof PlatformCameraDescription) { + stream.write(130); + writeValue(stream, ((PlatformCameraDescription) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface CameraApi { + + @NonNull + List getAvailableCameras(); + + /** The codec used by CameraApi. */ + static @NonNull MessageCodec getCodec() { + return PigeonCodec.INSTANCE; + } + /** Sets up an instance of `CameraApi` to handle messages through the `binaryMessenger`. */ + static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable CameraApi api) { + setUp(binaryMessenger, "", api); + } + + static void setUp( + @NonNull BinaryMessenger binaryMessenger, + @NonNull String messageChannelSuffix, + @Nullable CameraApi api) { + messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.camera_android.CameraApi.getAvailableCameras" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + List output = api.getAvailableCameras(); + wrapped.add(0, output); + } 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 index eae8c650fe60..ba80112a734e 100644 --- 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 @@ -25,11 +25,13 @@ 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 { +final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler, Messages.CameraApi { private final Activity activity; private final BinaryMessenger messenger; private final CameraPermissions cameraPermissions; @@ -55,18 +57,12 @@ final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { 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 "availableCameras": - try { - result.success(CameraUtils.getAvailableCameras(activity)); - } catch (Exception e) { - handleException(e, result); - } - break; case "create": { if (camera != null) { @@ -430,4 +426,17 @@ private void handleException(Exception exception, Result result) { // 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/CameraUtilsTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java index e59b05bf4fe3..bf6e6c4d203f 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 @@ -18,7 +18,6 @@ import android.hardware.camera2.CameraMetadata; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import java.util.List; -import java.util.Map; import org.junit.Test; public class CameraUtilsTest { @@ -87,14 +86,17 @@ public void getAvailableCameras_retrievesValidCameras() .thenReturn(mockSensorOrientation2) .thenReturn(mockLensFacing2); - List> availableCameras = CameraUtils.getAvailableCameras(mockActivity); + List availableCameras = + CameraUtils.getAvailableCameras(mockActivity); assertEquals(availableCameras.size(), 2); - assertEquals(availableCameras.get(0).get("name"), "1394902"); - assertEquals(availableCameras.get(0).get("sensorOrientation"), mockSensorOrientation0); - assertEquals(availableCameras.get(0).get("lensFacing"), "front"); - assertEquals(availableCameras.get(1).get("name"), "0283835"); - assertEquals(availableCameras.get(1).get("sensorOrientation"), mockSensorOrientation2); - assertEquals(availableCameras.get(1).get("lensFacing"), "external"); + assertEquals(availableCameras.get(0).getName(), "1394902"); + assertEquals(availableCameras.get(0).getSensorOrientation().intValue(), mockSensorOrientation0); + assertEquals( + availableCameras.get(0).getLensDirection(), Messages.PlatformCameraLensDirection.FRONT); + assertEquals(availableCameras.get(1).getName(), "0283835"); + assertEquals(availableCameras.get(1).getSensorOrientation().intValue(), mockSensorOrientation2); + assertEquals( + availableCameras.get(1).getLensDirection(), Messages.PlatformCameraLensDirection.EXTERNAL); } } diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index 9cd1df45191d..b5566090a07d 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -11,6 +11,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; +import 'messages.g.dart'; import 'type_conversion.dart'; import 'utils.dart'; @@ -19,6 +20,10 @@ const MethodChannel _channel = /// The Android implementation of [CameraPlatform] that uses method channels. class AndroidCamera extends CameraPlatform { + /// Creates a new [CameraPlatform] instance. + AndroidCamera({@visibleForTesting CameraApi? hostApi}) + : _hostApi = hostApi ?? CameraApi(); + /// Registers this class as the default instance of [CameraPlatform]. static void registerWith() { CameraPlatform.instance = AndroidCamera(); @@ -26,6 +31,8 @@ class AndroidCamera extends CameraPlatform { final Map _channels = {}; + final CameraApi _hostApi; + /// The name of the channel that device events from the platform side are /// sent on. @visibleForTesting @@ -71,20 +78,15 @@ class AndroidCamera extends CameraPlatform { @override Future> availableCameras() async { try { - final List>? cameras = await _channel - .invokeListMethod>('availableCameras'); - - if (cameras == null) { - return []; - } - - return cameras.map((Map camera) { + final List cameraDescriptions = + await _hostApi.getAvailableCameras(); + return cameraDescriptions + .map((PlatformCameraDescription cameraDescription) { return CameraDescription( - name: camera['name']! as String, - lensDirection: - parseCameraLensDirection(camera['lensFacing']! as String), - sensorOrientation: camera['sensorOrientation']! as int, - ); + name: cameraDescription.name, + lensDirection: cameraLensDirectionFromPlatform( + cameraDescription.lensDirection), + sensorOrientation: cameraDescription.sensorOrientation); }).toList(); } on PlatformException catch (e) { throw CameraException(e.code, e.message); diff --git a/packages/camera/camera_android/lib/src/messages.g.dart b/packages/camera/camera_android/lib/src/messages.g.dart new file mode 100644 index 000000000000..9ee641509c46 --- /dev/null +++ b/packages/camera/camera_android/lib/src/messages.g.dart @@ -0,0 +1,134 @@ +// 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. +// Autogenerated from Pigeon (v22.4.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +enum PlatformCameraLensDirection { + front, + back, + external, +} + +class PlatformCameraDescription { + PlatformCameraDescription({ + required this.name, + required this.lensDirection, + required this.sensorOrientation, + }); + + String name; + + PlatformCameraLensDirection lensDirection; + + int sensorOrientation; + + Object encode() { + return [ + name, + lensDirection, + sensorOrientation, + ]; + } + + static PlatformCameraDescription decode(Object result) { + result as List; + return PlatformCameraDescription( + name: result[0]! as String, + lensDirection: result[1]! as PlatformCameraLensDirection, + sensorOrientation: result[2]! as int, + ); + } +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is PlatformCameraLensDirection) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else if (value is PlatformCameraDescription) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformCameraLensDirection.values[value]; + case 130: + return PlatformCameraDescription.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class CameraApi { + /// Constructor for [CameraApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + CameraApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future> getAvailableCameras() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.camera_android.CameraApi.getAvailableCameras$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 List?)! + .cast(); + } + } +} diff --git a/packages/camera/camera_android/lib/src/utils.dart b/packages/camera/camera_android/lib/src/utils.dart index 8d58f7fe1297..9af1d97d6cad 100644 --- a/packages/camera/camera_android/lib/src/utils.dart +++ b/packages/camera/camera_android/lib/src/utils.dart @@ -5,17 +5,16 @@ import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; -/// Parses a string into a corresponding CameraLensDirection. -CameraLensDirection parseCameraLensDirection(String string) { - switch (string) { - case 'front': - return CameraLensDirection.front; - case 'back': - return CameraLensDirection.back; - case 'external': - return CameraLensDirection.external; - } - throw ArgumentError('Unknown CameraLensDirection value'); +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, + }; } /// Returns the device orientation as a String. diff --git a/packages/camera/camera_android/pigeons/copyright.txt b/packages/camera/camera_android/pigeons/copyright.txt new file mode 100644 index 000000000000..fb682b1ab965 --- /dev/null +++ b/packages/camera/camera_android/pigeons/copyright.txt @@ -0,0 +1,3 @@ +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. \ No newline at end of file diff --git a/packages/camera/camera_android/pigeons/messages.dart b/packages/camera/camera_android/pigeons/messages.dart new file mode 100644 index 000000000000..e66f62c02320 --- /dev/null +++ b/packages/camera/camera_android/pigeons/messages.dart @@ -0,0 +1,36 @@ +// 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. +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + javaOptions: JavaOptions(package: 'io.flutter.plugins.camera'), + javaOut: 'android/src/main/java/io/flutter/plugins/camera/Messages.java', + copyrightHeader: 'pigeons/copyright.txt', +)) + +/// Pigeon equivalent of [CameraLensDirection]. +enum PlatformCameraLensDirection { + front, + back, + external, +} + +/// Pigeon equivalent of [CameraDescription]. +class PlatformCameraDescription { + PlatformCameraDescription( + {required this.name, + required this.lensDirection, + required this.sensorOrientation}); + final String name; + final PlatformCameraLensDirection lensDirection; + final int sensorOrientation; +} + +@HostApi() +abstract class CameraApi { + /// Returns the list of available cameras. + List getAvailableCameras(); +} diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index c65e2b07e8fe..5d3e369ad477 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+12 +version: 0.10.9+13 environment: sdk: ^3.5.0 @@ -27,8 +27,11 @@ dependencies: dev_dependencies: async: ^2.5.0 + build_runner: ^2.4.11 flutter_test: sdk: flutter + mockito: ^5.4.4 + pigeon: ^22.4.1 topics: - camera diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index 04e2e256837c..17b8904f09bd 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -7,16 +7,21 @@ import 'dart:math'; import 'package:async/async.dart'; import 'package:camera_android/src/android_camera.dart'; +import 'package:camera_android/src/messages.g.dart'; import 'package:camera_android/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +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]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -490,6 +495,7 @@ void main() { group('Function Tests', () { late AndroidCamera camera; late int cameraId; + late MockCameraApi mockCameraApi; setUp(() async { MethodChannelMock( @@ -499,7 +505,8 @@ void main() { 'initialize': null }, ); - camera = AndroidCamera(); + mockCameraApi = MockCameraApi(); + camera = AndroidCamera(hostApi: mockCameraApi); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', @@ -526,40 +533,33 @@ void main() { test('Should fetch CameraDescription instances for available cameras', () async { // Arrange - final List returnData = [ - { - 'name': 'Test 1', - 'lensFacing': 'front', - 'sensorOrientation': 1 - }, - { - 'name': 'Test 2', - 'lensFacing': 'back', - 'sensorOrientation': 2 - } + final List returnData = + [ + PlatformCameraDescription( + name: 'Test 1', + lensDirection: PlatformCameraLensDirection.front, + sensorOrientation: 1), + PlatformCameraDescription( + name: 'Test 2', + lensDirection: PlatformCameraLensDirection.back, + sensorOrientation: 2), ]; - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'availableCameras': returnData}, - ); + when(mockCameraApi.getAvailableCameras()) + .thenAnswer((_) async => returnData); // Act final List cameras = await camera.availableCameras(); // Assert - expect(channel.log, [ - isMethodCall('availableCameras', arguments: null), - ]); expect(cameras.length, returnData.length); for (int i = 0; i < returnData.length; i++) { - final Map typedData = - (returnData[i] as Map).cast(); + final PlatformCameraDescription platformCameraDescription = + returnData[i]; final CameraDescription cameraDescription = CameraDescription( - name: typedData['name']! as String, - lensDirection: - parseCameraLensDirection(typedData['lensFacing']! as String), - sensorOrientation: typedData['sensorOrientation']! as int, - ); + name: platformCameraDescription.name, + lensDirection: cameraLensDirectionFromPlatform( + platformCameraDescription.lensDirection), + sensorOrientation: platformCameraDescription.sensorOrientation); expect(cameras[i], cameraDescription); } }); @@ -568,12 +568,9 @@ void main() { 'Should throw CameraException when availableCameras throws a PlatformException', () { // Arrange - MethodChannelMock(channelName: _channelName, methods: { - 'availableCameras': PlatformException( + when(mockCameraApi.getAvailableCameras()).thenThrow(PlatformException( code: 'TESTING_ERROR_CODE', - message: 'Mock error message used during testing.', - ) - }); + message: 'Mock error message used during testing.')); // Act expect( diff --git a/packages/camera/camera_android/test/android_camera_test.mocks.dart b/packages/camera/camera_android/test/android_camera_test.mocks.dart new file mode 100644 index 000000000000..c833a265e368 --- /dev/null +++ b/packages/camera/camera_android/test/android_camera_test.mocks.dart @@ -0,0 +1,52 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in camera_android/test/android_camera_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:camera_android/src/messages.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [CameraApi]. +/// +/// 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), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + ) as String); + + @override + _i4.Future> getAvailableCameras() => + (super.noSuchMethod( + Invocation.method( + #getAvailableCameras, + [], + ), + returnValue: _i4.Future>.value( + <_i2.PlatformCameraDescription>[]), + ) as _i4.Future>); +} diff --git a/packages/camera/camera_android/test/utils_test.dart b/packages/camera/camera_android/test/utils_test.dart index 6f426bc90f6f..81032101f18d 100644 --- a/packages/camera/camera_android/test/utils_test.dart +++ b/packages/camera/camera_android/test/utils_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_android/src/messages.g.dart'; import 'package:camera_android/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; @@ -13,28 +14,19 @@ void main() { 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', () { expect( - parseCameraLensDirection('back'), + cameraLensDirectionFromPlatform(PlatformCameraLensDirection.back), CameraLensDirection.back, ); expect( - parseCameraLensDirection('front'), + cameraLensDirectionFromPlatform(PlatformCameraLensDirection.front), CameraLensDirection.front, ); expect( - parseCameraLensDirection('external'), + cameraLensDirectionFromPlatform(PlatformCameraLensDirection.external), CameraLensDirection.external, ); }); - test( - 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', - () { - expect( - () => parseCameraLensDirection('test'), - throwsA(isArgumentError), - ); - }); - test('serializeDeviceOrientation() should serialize correctly', () { expect(serializeDeviceOrientation(DeviceOrientation.portraitUp), 'portraitUp');