[camera] Fix event type check (#2123)

Trivial change to properly check if the type of an event is an error, so a description can be added.
diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md
index c32ef10..2fe0e44 100644
--- a/packages/camera/CHANGELOG.md
+++ b/packages/camera/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.5+1
+
+* Fix event type check
+
 ## 0.5.5
 
 * Define clang modules for iOS.
diff --git a/packages/camera/android/build.gradle b/packages/camera/android/build.gradle
index dd544c0..ab2fc8f 100644
--- a/packages/camera/android/build.gradle
+++ b/packages/camera/android/build.gradle
@@ -52,4 +52,11 @@
         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/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
index 80da644..754a157 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 @@
   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 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 @@
 
     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 @@
     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 @@
 
           @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 @@
               default:
                 errorDescription = "Unknown camera error";
             }
-            sendEvent(EventType.ERROR, errorDescription);
+            dartMessenger.send(DartMessenger.EventType.ERROR, errorDescription);
           }
         },
         null);
@@ -327,7 +313,8 @@
           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 @@
                 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 @@
         null);
   }
 
-  private void sendEvent(EventType eventType) {
-    sendEvent(eventType, null);
-  }
-
-  private void sendEvent(EventType eventType, String description) {
-    if (eventSink != null) {
-      Map<String, String> 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 @@
             : (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 b3a1da8..b504f03 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 @@
     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 0000000..fe385be
--- /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<String, String> 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);
+  }
+}
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 0000000..db89eb2
--- /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<ByteBuffer> 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<ByteBuffer> 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<ByteBuffer> sentMessages = fakeBinaryMessenger.getMessages();
+    assertEquals(0, sentMessages.size());
+  }
+
+  @Test
+  public void send_includesErrorDescriptions() {
+    initializeEventSink();
+
+    dartMessenger.send(DartMessenger.EventType.ERROR, "error description");
+
+    List<ByteBuffer> sentMessages = fakeBinaryMessenger.getMessages();
+    assertEquals(1, sentMessages.size());
+    Map<String, String> 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<ByteBuffer> sentMessages = fakeBinaryMessenger.getMessages();
+    assertEquals(1, sentMessages.size());
+    Map<String, String> event = decodeSentMessage(sentMessages.get(0));
+    assertEquals(
+        DartMessenger.EventType.CAMERA_CLOSING.toString().toLowerCase(), event.get("eventType"));
+    assertNull(event.get("errorDescription"));
+  }
+
+  private Map<String, String> decodeSentMessage(ByteBuffer sentMessage) {
+    sentMessage.position(0);
+    return (Map<String, String>) 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 -> {});
+  }
+}
diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml
index 9ed9b55..0906f17 100644
--- a/packages/camera/pubspec.yaml
+++ b/packages/camera/pubspec.yaml
@@ -2,7 +2,7 @@
 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.5
+version: 0.5.5+1
 
 authors:
   - Flutter Team <flutter-dev@googlegroups.com>