From ad9f732c2be65a48a897ed8bc220dc118b93d57c Mon Sep 17 00:00:00 2001 From: Cristian Zazo Date: Mon, 30 Sep 2019 15:10:16 +0200 Subject: [PATCH 1/5] Fix event type check --- .../android/src/main/java/io/flutter/plugins/camera/Camera.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 80da644a146a..83fce9383836 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -496,7 +496,7 @@ private void sendEvent(EventType eventType, String description) { Map event = new HashMap<>(); event.put("eventType", eventType.toString().toLowerCase()); // Only errors have description - if (eventType != EventType.ERROR) { + if (eventType == EventType.ERROR) { event.put("errorDescription", description); } eventSink.success(event); From f6916762f9fee97b2ddc0479957825557d0407e0 Mon Sep 17 00:00:00 2001 From: Cristian Zazo Date: Mon, 30 Sep 2019 15:13:51 +0200 Subject: [PATCH 2/5] Update pubspec.yaml --- packages/camera/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index 9640da5ff166..a7213ad5ea9c 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.5.4+3 +version: 0.5.4+4 authors: - Flutter Team From 50eccc3bcfe25e08c2880b3fc66a9874c8b757ef Mon Sep 17 00:00:00 2001 From: Cristian Zazo Date: Mon, 30 Sep 2019 15:16:17 +0200 Subject: [PATCH 3/5] Update CHANGELOG.md --- packages/camera/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index b6c68aa7cf9f..314b02d77ab3 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.4+4 + +* Fix event type check + ## 0.5.4+3 * Update and migrate iOS example project. From 819d2b228406610d274befa294b03cc94a0dc633 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 1 Oct 2019 15:28:18 -0700 Subject: [PATCH 4/5] Refactor the message callback for testability Break out the functionality related to sending Dart callbacks into its own class, DartMessenger. This will let us test the former `sendEvent` without needing to initialize the camera hardware. --- .../io/flutter/plugins/camera/Camera.java | 59 ++++--------------- .../flutter/plugins/camera/CameraPlugin.java | 19 +++--- .../flutter/plugins/camera/DartMessenger.java | 51 ++++++++++++++++ 3 files changed, 76 insertions(+), 53 deletions(-) create mode 100644 packages/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 83fce9383836..754a157a8b71 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -28,7 +28,6 @@ import androidx.annotation.NonNull; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.view.FlutterView; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; import java.io.File; import java.io.FileOutputStream; @@ -55,7 +54,7 @@ public class Camera { private CameraCaptureSession cameraCaptureSession; private ImageReader pictureImageReader; private ImageReader imageStreamReader; - private EventChannel.EventSink eventSink; + private DartMessenger dartMessenger; private CaptureRequest.Builder captureRequestBuilder; private MediaRecorder mediaRecorder; private boolean recordingVideo; @@ -74,7 +73,8 @@ public enum ResolutionPreset { public Camera( final Activity activity, - final FlutterView flutterView, + final SurfaceTextureEntry flutterTexture, + final DartMessenger dartMessenger, final String cameraName, final String resolutionPreset, final boolean enableAudio) @@ -85,7 +85,8 @@ public Camera( this.cameraName = cameraName; this.enableAudio = enableAudio; - this.flutterTexture = flutterView.createSurfaceTexture(); + this.flutterTexture = flutterTexture; + this.dartMessenger = dartMessenger; this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); orientationEventListener = new OrientationEventListener(activity.getApplicationContext()) { @@ -115,21 +116,6 @@ public void onOrientationChanged(int i) { previewSize = computeBestPreviewSize(cameraName, preset); } - public void setupCameraEventChannel(EventChannel cameraEventChannel) { - cameraEventChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object arguments, EventChannel.EventSink sink) { - eventSink = sink; - } - - @Override - public void onCancel(Object arguments) { - eventSink = null; - } - }); - } - private void prepareMediaRecorder(String outputFilePath) throws IOException { if (mediaRecorder != null) { mediaRecorder.release(); @@ -186,14 +172,14 @@ public void onOpened(@NonNull CameraDevice device) { @Override public void onClosed(@NonNull CameraDevice camera) { - sendEvent(EventType.CAMERA_CLOSING); + dartMessenger.sendCameraClosingEvent(); super.onClosed(camera); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { close(); - sendEvent(EventType.ERROR, "The camera was disconnected."); + dartMessenger.send(DartMessenger.EventType.ERROR, "The camera was disconnected."); } @Override @@ -219,7 +205,7 @@ public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { default: errorDescription = "Unknown camera error"; } - sendEvent(EventType.ERROR, errorDescription); + dartMessenger.send(DartMessenger.EventType.ERROR, errorDescription); } }, null); @@ -327,7 +313,8 @@ private void createCaptureSession( public void onConfigured(@NonNull CameraCaptureSession session) { try { if (cameraDevice == null) { - sendEvent(EventType.ERROR, "The camera was closed during configuration."); + dartMessenger.send( + DartMessenger.EventType.ERROR, "The camera was closed during configuration."); return; } cameraCaptureSession = session; @@ -338,13 +325,14 @@ public void onConfigured(@NonNull CameraCaptureSession session) { onSuccessCallback.run(); } } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) { - sendEvent(EventType.ERROR, e.getMessage()); + dartMessenger.send(DartMessenger.EventType.ERROR, e.getMessage()); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - sendEvent(EventType.ERROR, "Failed to configure camera session."); + dartMessenger.send( + DartMessenger.EventType.ERROR, "Failed to configure camera session."); } }; @@ -487,22 +475,6 @@ private void setImageStreamImageAvailableListener(final EventChannel.EventSink i null); } - private void sendEvent(EventType eventType) { - sendEvent(eventType, null); - } - - private void sendEvent(EventType eventType, String description) { - if (eventSink != null) { - Map event = new HashMap<>(); - event.put("eventType", eventType.toString().toLowerCase()); - // Only errors have description - if (eventType == EventType.ERROR) { - event.put("errorDescription", description); - } - eventSink.success(event); - } - } - private void closeCaptureSession() { if (cameraCaptureSession != null) { cameraCaptureSession.close(); @@ -545,9 +517,4 @@ private int getMediaOrientation() { : (isFrontFacing) ? -currentOrientation : currentOrientation; return (sensorOrientationOffset + sensorOrientation + 360) % 360; } - - private enum EventType { - ERROR, - CAMERA_CLOSING, - } } diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java index b3a1da8b1b09..b504f039e326 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java @@ -14,6 +14,7 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.view.FlutterView; +import io.flutter.view.TextureRegistry; public class CameraPlugin implements MethodCallHandler { @@ -48,13 +49,17 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce String cameraName = call.argument("cameraName"); String resolutionPreset = call.argument("resolutionPreset"); boolean enableAudio = call.argument("enableAudio"); - camera = new Camera(registrar.activity(), view, cameraName, resolutionPreset, enableAudio); - - EventChannel cameraEventChannel = - new EventChannel( - registrar.messenger(), - "flutter.io/cameraPlugin/cameraEvents" + camera.getFlutterTexture().id()); - camera.setupCameraEventChannel(cameraEventChannel); + TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = view.createSurfaceTexture(); + DartMessenger dartMessenger = + new DartMessenger(registrar.messenger(), flutterSurfaceTexture.id()); + camera = + new Camera( + registrar.activity(), + flutterSurfaceTexture, + dartMessenger, + cameraName, + resolutionPreset, + enableAudio); camera.open(result); } diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java new file mode 100644 index 000000000000..fe385bef7818 --- /dev/null +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -0,0 +1,51 @@ +package io.flutter.plugins.camera; + +import android.text.TextUtils; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.EventChannel; +import java.util.HashMap; +import java.util.Map; + +class DartMessenger { + @Nullable private EventChannel.EventSink eventSink; + + enum EventType { + ERROR, + CAMERA_CLOSING, + } + + DartMessenger(BinaryMessenger messenger, long eventChannelId) { + new EventChannel(messenger, "flutter.io/cameraPlugin/cameraEvents" + eventChannelId) + .setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object arguments, EventChannel.EventSink sink) { + eventSink = sink; + } + + @Override + public void onCancel(Object arguments) { + eventSink = null; + } + }); + } + + void sendCameraClosingEvent() { + send(EventType.CAMERA_CLOSING, null); + } + + void send(EventType eventType, @Nullable String description) { + if (eventSink == null) { + return; + } + + Map event = new HashMap<>(); + event.put("eventType", eventType.toString().toLowerCase()); + // Only errors have a description. + if (eventType == EventType.ERROR && !TextUtils.isEmpty(description)) { + event.put("errorDescription", description); + } + eventSink.success(event); + } +} From 489867eb0a047e239bb76e1898577af6145084e3 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 1 Oct 2019 16:44:05 -0700 Subject: [PATCH 5/5] Add a unit test for DartMessenger --- packages/camera/android/build.gradle | 7 ++ .../plugins/camera/DartMessengerTest.java | 107 ++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 packages/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java diff --git a/packages/camera/android/build.gradle b/packages/camera/android/build.gradle index dd544c084ba7..ab2fc8fd89f0 100644 --- a/packages/camera/android/build.gradle +++ b/packages/camera/android/build.gradle @@ -52,4 +52,11 @@ android { implementation 'androidx.annotation:annotation:1.0.0' implementation 'androidx.core:core:1.0.0' } + testOptions { + unitTests.returnDefaultValues = true + } +} + +dependencies { + testImplementation 'junit:junit:4.12' } diff --git a/packages/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java new file mode 100644 index 000000000000..db89eb279f41 --- /dev/null +++ b/packages/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -0,0 +1,107 @@ +package io.flutter.plugins.camera; + +import static junit.framework.TestCase.assertNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.StandardMethodCodec; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; + +public class DartMessengerTest { + /** A {@link BinaryMessenger} implementation that does nothing but save its messages. */ + private static class FakeBinaryMessenger implements BinaryMessenger { + private BinaryMessageHandler handler; + private final List sentMessages = new ArrayList<>(); + + @Override + public void send(String channel, ByteBuffer message) { + sentMessages.add(message); + } + + @Override + public void send(String channel, ByteBuffer message, BinaryReply callback) { + send(channel, message); + } + + @Override + public void setMessageHandler(String channel, BinaryMessageHandler handler) { + this.handler = handler; + } + + BinaryMessageHandler getMessageHandler() { + return handler; + } + + List getMessages() { + return new ArrayList<>(sentMessages); + } + } + + private DartMessenger dartMessenger; + private FakeBinaryMessenger fakeBinaryMessenger; + + @Before + public void setUp() { + fakeBinaryMessenger = new FakeBinaryMessenger(); + dartMessenger = new DartMessenger(fakeBinaryMessenger, 0); + } + + @Test + public void setsStreamHandler() { + assertNotNull(fakeBinaryMessenger.getMessageHandler()); + } + + @Test + public void send_handlesNullEventSinks() { + dartMessenger.send(DartMessenger.EventType.ERROR, "error description"); + + List sentMessages = fakeBinaryMessenger.getMessages(); + assertEquals(0, sentMessages.size()); + } + + @Test + public void send_includesErrorDescriptions() { + initializeEventSink(); + + dartMessenger.send(DartMessenger.EventType.ERROR, "error description"); + + List sentMessages = fakeBinaryMessenger.getMessages(); + assertEquals(1, sentMessages.size()); + Map event = decodeSentMessage(sentMessages.get(0)); + assertEquals(DartMessenger.EventType.ERROR.toString().toLowerCase(), event.get("eventType")); + assertEquals("error description", event.get("errorDescription")); + } + + @Test + public void sendCameraClosingEvent() { + initializeEventSink(); + + dartMessenger.sendCameraClosingEvent(); + + List sentMessages = fakeBinaryMessenger.getMessages(); + assertEquals(1, sentMessages.size()); + Map event = decodeSentMessage(sentMessages.get(0)); + assertEquals( + DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), event.get("eventType")); + assertNull(event.get("errorDescription")); + } + + private Map decodeSentMessage(ByteBuffer sentMessage) { + sentMessage.position(0); + return (Map) StandardMethodCodec.INSTANCE.decodeEnvelope(sentMessage); + } + + private void initializeEventSink() { + MethodCall call = new MethodCall("listen", null); + ByteBuffer encodedCall = StandardMethodCodec.INSTANCE.encodeMethodCall(call); + encodedCall.position(0); + fakeBinaryMessenger.getMessageHandler().onMessage(encodedCall, reply -> {}); + } +}