diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java new file mode 100644 index 000000000000..21dcb602655d --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java @@ -0,0 +1,168 @@ +// 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.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCaptureSession.CaptureCallback; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.util.Log; +import androidx.annotation.NonNull; +import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper; + +/** + * A callback object for tracking the progress of a {@link android.hardware.camera2.CaptureRequest} + * submitted to the camera device. + */ +class CameraCaptureCallback extends CaptureCallback { + private static final String TAG = "CameraCaptureCallback"; + private final CameraCaptureStateListener cameraStateListener; + private CameraState cameraState; + private final CaptureTimeoutsWrapper captureTimeouts; + + private CameraCaptureCallback( + @NonNull CameraCaptureStateListener cameraStateListener, + @NonNull CaptureTimeoutsWrapper captureTimeouts) { + cameraState = CameraState.STATE_PREVIEW; + this.cameraStateListener = cameraStateListener; + this.captureTimeouts = captureTimeouts; + } + + /** + * Creates a new instance of the {@link CameraCaptureCallback} class. + * + * @param cameraStateListener instance which will be called when the camera state changes. + * @param captureTimeouts specifying the different timeout counters that should be taken into + * account. + * @return a configured instance of the {@link CameraCaptureCallback} class. + */ + public static CameraCaptureCallback create( + @NonNull CameraCaptureStateListener cameraStateListener, + @NonNull CaptureTimeoutsWrapper captureTimeouts) { + return new CameraCaptureCallback(cameraStateListener, captureTimeouts); + } + + /** + * Gets the current {@link CameraState}. + * + * @return the current {@link CameraState}. + */ + public CameraState getCameraState() { + return cameraState; + } + + /** + * Sets the {@link CameraState}. + * + * @param state the camera is currently in. + */ + public void setCameraState(@NonNull CameraState state) { + cameraState = state; + } + + private void process(CaptureResult result) { + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + + if (cameraState != CameraState.STATE_PREVIEW) { + Log.d( + TAG, + "CameraCaptureCallback | state: " + + cameraState + + " | afState: " + + afState + + " | aeState: " + + aeState); + } + + switch (cameraState) { + case STATE_PREVIEW: + { + // We have nothing to do when the camera preview is working normally. + break; + } + case STATE_WAITING_FOCUS: + { + if (afState == null) { + return; + } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED + || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { + handleWaitingFocusState(aeState); + } else if (captureTimeouts.getPreCaptureFocusing().getIsExpired()) { + Log.w(TAG, "Focus timeout, moving on with capture"); + handleWaitingFocusState(aeState); + } + + break; + } + case STATE_WAITING_PRECAPTURE_START: + { + // CONTROL_AE_STATE can be null on some devices + if (aeState == null + || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED + || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE + || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) { + setCameraState(CameraState.STATE_WAITING_PRECAPTURE_DONE); + } else if (captureTimeouts.getPreCaptureMetering().getIsExpired()) { + Log.w(TAG, "Metering timeout waiting for pre-capture to start, moving on with capture"); + + setCameraState(CameraState.STATE_WAITING_PRECAPTURE_DONE); + } + break; + } + case STATE_WAITING_PRECAPTURE_DONE: + { + // CONTROL_AE_STATE can be null on some devices + if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { + cameraStateListener.onConverged(); + } else if (captureTimeouts.getPreCaptureMetering().getIsExpired()) { + Log.w( + TAG, "Metering timeout waiting for pre-capture to finish, moving on with capture"); + cameraStateListener.onConverged(); + } + + break; + } + } + } + + private void handleWaitingFocusState(Integer aeState) { + // CONTROL_AE_STATE can be null on some devices + if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { + cameraStateListener.onConverged(); + } else { + cameraStateListener.onPrecapture(); + } + } + + @Override + public void onCaptureProgressed( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureResult partialResult) { + process(partialResult); + } + + @Override + public void onCaptureCompleted( + @NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + process(result); + } + + /** An interface that describes the different state changes implementers can be informed about. */ + interface CameraCaptureStateListener { + + /** Called when the {@link android.hardware.camera2.CaptureRequest} has been converged. */ + void onConverged(); + + /** + * Called when the {@link android.hardware.camera2.CaptureRequest} enters the pre-capture state. + */ + void onPrecapture(); + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java index a69ddd0410d4..95efebbf6488 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java @@ -124,7 +124,7 @@ public interface CameraProperties { *
By default maps to the @see android.hardware.camera2.CameraCharacteristics.LENS_FACING key. * * @return int Direction the camera faces relative to device screen. */ @@ -216,7 +216,7 @@ public interface CameraProperties { *
By default maps to the @see
* android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL key.
*
* @return int Level which generally classifies the overall set of the camera device
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraState.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraState.java
new file mode 100644
index 000000000000..ac48caf18ac6
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraState.java
@@ -0,0 +1,27 @@
+// 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;
+
+/**
+ * These are the states that the camera can be in. The camera can only take one photo at a time so
+ * this state describes the state of the camera itself. The camera works like a pipeline where we
+ * feed it requests through. It can only process one tasks at a time.
+ */
+public enum CameraState {
+ /** Idle, showing preview and not capturing anything. */
+ STATE_PREVIEW,
+
+ /** Starting and waiting for autofocus to complete. */
+ STATE_WAITING_FOCUS,
+
+ /** Start performing autoexposure. */
+ STATE_WAITING_PRECAPTURE_START,
+
+ /** waiting for autoexposure to complete. */
+ STATE_WAITING_PRECAPTURE_DONE,
+
+ /** Capturing an image. */
+ STATE_CAPTURING,
+}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java
index 37bfbf294663..93b963e65821 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java
@@ -16,12 +16,15 @@
import java.util.HashMap;
import java.util.Map;
+/** Utility class that facilitates communication to the Flutter client */
public class DartMessenger {
@NonNull private final Handler handler;
@Nullable private MethodChannel cameraChannel;
@Nullable private MethodChannel deviceChannel;
+ /** Specifies the different device related message types. */
enum DeviceEventType {
+ /** Indicates the device's orientation has changed. */
ORIENTATION_CHANGED("orientation_changed");
private final String method;
@@ -30,24 +33,47 @@ enum DeviceEventType {
}
}
+ /** Specifies the different camera related message types. */
enum CameraEventType {
+ /** Indicates that an error occurred while interacting with the camera. */
ERROR("error"),
+ /** Indicates that the camera is closing. */
CLOSING("camera_closing"),
+ /** Indicates that the camera is initialized. */
INITIALIZED("initialized");
private final String method;
+ /**
+ * Converts the supplied method name to the matching {@link CameraEventType}.
+ *
+ * @param method name to be converted into a {@link CameraEventType}.
+ */
CameraEventType(String method) {
this.method = method;
}
}
+ /**
+ * Creates a new instance of the {@link DartMessenger} class.
+ *
+ * @param messenger is the {@link BinaryMessenger} that is used to communicate with Flutter.
+ * @param cameraId identifies the camera which is the source of the communication.
+ * @param handler the handler used to manage the thread's message queue. This should always be a
+ * handler managing the main thread since communication with Flutter should always happen on
+ * the main thread. The handler is mainly supplied so it will be easier test this class.
+ */
DartMessenger(BinaryMessenger messenger, long cameraId, @NonNull Handler handler) {
cameraChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId);
deviceChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/device");
this.handler = handler;
}
+ /**
+ * Sends a message to the Flutter client informing the orientation of the device has been changed.
+ *
+ * @param orientation specifies the new orientation of the device.
+ */
public void sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation orientation) {
assert (orientation != null);
this.send(
@@ -59,6 +85,16 @@ public void sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation o
});
}
+ /**
+ * Sends a message to the Flutter client informing that the camera has been initialized.
+ *
+ * @param previewWidth describes the preview width that is supported by the camera.
+ * @param previewHeight describes the preview height that is supported by the camera.
+ * @param exposureMode describes the current exposure mode that is set on the camera.
+ * @param focusMode describes the current focus mode that is set on the camera.
+ * @param exposurePointSupported indicates if the camera supports setting an exposure point.
+ * @param focusPointSupported indicates if the camera supports setting a focus point.
+ */
void sendCameraInitializedEvent(
Integer previewWidth,
Integer previewHeight,
@@ -86,10 +122,17 @@ void sendCameraInitializedEvent(
});
}
+ /** Sends a message to the Flutter client informing that the camera is closing. */
void sendCameraClosingEvent() {
send(CameraEventType.CLOSING);
}
+ /**
+ * Sends a message to the Flutter client informing that an error occurred while interacting with
+ * the camera.
+ *
+ * @param description contains details regarding the error that occurred.
+ */
void sendCameraErrorEvent(@Nullable String description) {
this.send(
CameraEventType.ERROR,
@@ -100,11 +143,11 @@ void sendCameraErrorEvent(@Nullable String description) {
});
}
- void send(CameraEventType eventType) {
+ private void send(CameraEventType eventType) {
send(eventType, new HashMap<>());
}
- void send(CameraEventType eventType, Map This method is visible for testing purposes only and should never be used outside this *
+ * class.
+ *
+ * @param file - The file to create the output stream for
+ * @return new instance of the {@link FileOutputStream} class.
+ * @throws FileNotFoundException when the supplied file could not be found.
+ */
+ @VisibleForTesting
+ public static FileOutputStream create(File file) throws FileNotFoundException {
+ return new FileOutputStream(file);
+ }
+ }
+}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java
new file mode 100644
index 000000000000..8d10c445788c
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java
@@ -0,0 +1,141 @@
+// 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.features;
+
+import android.app.Activity;
+import androidx.annotation.NonNull;
+import io.flutter.plugins.camera.CameraProperties;
+import io.flutter.plugins.camera.DartMessenger;
+import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
+import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
+import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
+import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature;
+import io.flutter.plugins.camera.features.flash.FlashFeature;
+import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
+import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
+import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
+import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
+
+/**
+ * Factory for creating the supported feature implementation controlling different aspects of the
+ * {@link android.hardware.camera2.CaptureRequest}.
+ */
+public interface CameraFeatureFactory {
+
+ /**
+ * Creates a new instance of the auto focus feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @param recordingVideo indicates if the camera is currently recording.
+ * @return newly created instance of the AutoFocusFeature class.
+ */
+ AutoFocusFeature createAutoFocusFeature(
+ @NonNull CameraProperties cameraProperties, boolean recordingVideo);
+
+ /**
+ * Creates a new instance of the exposure lock feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @return newly created instance of the ExposureLockFeature class.
+ */
+ ExposureLockFeature createExposureLockFeature(@NonNull CameraProperties cameraProperties);
+
+ /**
+ * Creates a new instance of the exposure offset feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @return newly created instance of the ExposureOffsetFeature class.
+ */
+ ExposureOffsetFeature createExposureOffsetFeature(@NonNull CameraProperties cameraProperties);
+
+ /**
+ * Creates a new instance of the flash feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @return newly created instance of the FlashFeature class.
+ */
+ FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties);
+
+ /**
+ * Creates a new instance of the resolution feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @param initialSetting initial resolution preset.
+ * @param cameraName the name of the camera which can be used to identify the camera device.
+ * @return newly created instance of the ResolutionFeature class.
+ */
+ ResolutionFeature createResolutionFeature(
+ @NonNull CameraProperties cameraProperties,
+ ResolutionPreset initialSetting,
+ String cameraName);
+
+ /**
+ * Creates a new instance of the focus point feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @return newly created instance of the FocusPointFeature class.
+ */
+ FocusPointFeature createFocusPointFeature(@NonNull CameraProperties cameraProperties);
+
+ /**
+ * Creates a new instance of the FPS range feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @return newly created instance of the FpsRangeFeature class.
+ */
+ FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties);
+
+ /**
+ * Creates a new instance of the sensor orientation feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @param activity current activity associated with the camera plugin.
+ * @param dartMessenger instance of the DartMessenger class, used to send state updates back to
+ * Dart.
+ * @return newly created instance of the SensorOrientationFeature class.
+ */
+ SensorOrientationFeature createSensorOrientationFeature(
+ @NonNull CameraProperties cameraProperties,
+ @NonNull Activity activity,
+ @NonNull DartMessenger dartMessenger);
+
+ /**
+ * Creates a new instance of the zoom level feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @return newly created instance of the ZoomLevelFeature class.
+ */
+ ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties);
+
+ /**
+ * Creates a new instance of the exposure point feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @return newly created instance of the ExposurePointFeature class.
+ */
+ ExposurePointFeature createExposurePointFeature(@NonNull CameraProperties cameraProperties);
+
+ /**
+ * Creates a new instance of the noise reduction feature.
+ *
+ * @param cameraProperties instance of the CameraProperties class containing information about the
+ * cameras features.
+ * @return newly created instance of the NoiseReductionFeature class.
+ */
+ NoiseReductionFeature createNoiseReductionFeature(@NonNull CameraProperties cameraProperties);
+}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java
new file mode 100644
index 000000000000..b12ad3626226
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java
@@ -0,0 +1,95 @@
+// 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.features;
+
+import android.app.Activity;
+import androidx.annotation.NonNull;
+import io.flutter.plugins.camera.CameraProperties;
+import io.flutter.plugins.camera.DartMessenger;
+import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
+import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
+import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
+import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature;
+import io.flutter.plugins.camera.features.flash.FlashFeature;
+import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
+import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
+import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
+import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
+
+/**
+ * Implementation of the {@link CameraFeatureFactory} interface creating the supported feature
+ * implementation controlling different aspects of the {@link
+ * android.hardware.camera2.CaptureRequest}.
+ */
+public class CameraFeatureFactoryImpl implements CameraFeatureFactory {
+
+ @Override
+ public AutoFocusFeature createAutoFocusFeature(
+ @NonNull CameraProperties cameraProperties, boolean recordingVideo) {
+ return new AutoFocusFeature(cameraProperties, recordingVideo);
+ }
+
+ @Override
+ public ExposureLockFeature createExposureLockFeature(@NonNull CameraProperties cameraProperties) {
+ return new ExposureLockFeature(cameraProperties);
+ }
+
+ @Override
+ public ExposureOffsetFeature createExposureOffsetFeature(
+ @NonNull CameraProperties cameraProperties) {
+ return new ExposureOffsetFeature(cameraProperties);
+ }
+
+ @Override
+ public FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties) {
+ return new FlashFeature(cameraProperties);
+ }
+
+ @Override
+ public ResolutionFeature createResolutionFeature(
+ @NonNull CameraProperties cameraProperties,
+ ResolutionPreset initialSetting,
+ String cameraName) {
+ return new ResolutionFeature(cameraProperties, initialSetting, cameraName);
+ }
+
+ @Override
+ public FocusPointFeature createFocusPointFeature(@NonNull CameraProperties cameraProperties) {
+ return new FocusPointFeature(cameraProperties);
+ }
+
+ @Override
+ public FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties) {
+ return new FpsRangeFeature(cameraProperties);
+ }
+
+ @Override
+ public SensorOrientationFeature createSensorOrientationFeature(
+ @NonNull CameraProperties cameraProperties,
+ @NonNull Activity activity,
+ @NonNull DartMessenger dartMessenger) {
+ return new SensorOrientationFeature(cameraProperties, activity, dartMessenger);
+ }
+
+ @Override
+ public ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties) {
+ return new ZoomLevelFeature(cameraProperties);
+ }
+
+ @Override
+ public ExposurePointFeature createExposurePointFeature(
+ @NonNull CameraProperties cameraProperties) {
+ return new ExposurePointFeature(cameraProperties);
+ }
+
+ @Override
+ public NoiseReductionFeature createNoiseReductionFeature(
+ @NonNull CameraProperties cameraProperties) {
+ return new NoiseReductionFeature(cameraProperties);
+ }
+}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java
new file mode 100644
index 000000000000..0ee8969071bc
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java
@@ -0,0 +1,248 @@
+// 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.features;
+
+import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
+import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
+import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
+import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature;
+import io.flutter.plugins.camera.features.flash.FlashFeature;
+import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
+import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
+import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature;
+import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
+import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
+import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * These are all of our available features in the camera. Used in the Camera to access all features
+ * in a simpler way.
+ */
+public class CameraFeatures {
+ private static final String AUTO_FOCUS = "AUTO_FOCUS";
+ private static final String EXPOSURE_LOCK = "EXPOSURE_LOCK";
+ private static final String EXPOSURE_OFFSET = "EXPOSURE_OFFSET";
+ private static final String EXPOSURE_POINT = "EXPOSURE_POINT";
+ private static final String FLASH = "FLASH";
+ private static final String FOCUS_POINT = "FOCUS_POINT";
+ private static final String FPS_RANGE = "FPS_RANGE";
+ private static final String NOISE_REDUCTION = "NOISE_REDUCTION";
+ private static final String REGION_BOUNDARIES = "REGION_BOUNDARIES";
+ private static final String RESOLUTION = "RESOLUTION";
+ private static final String SENSOR_ORIENTATION = "SENSOR_ORIENTATION";
+ private static final String ZOOM_LEVEL = "ZOOM_LEVEL";
+
+ private Map We use timeouts to ensure a picture is always captured within a reasonable amount of time even
+ * if the settings don't converge and focus can't be locked.
+ *
+ * You generally check the status of the timeout in the CameraCaptureCallback during the capture
+ * sequence and use it to move to the next state if the timeout has passed.
+ */
+public class Timeout {
+
+ /** The timeout time in milliseconds */
+ private final long timeoutMs;
+
+ /** When this timeout was started. Will be used later to check if the timeout has expired yet. */
+ private final long timeStarted;
+
+ /**
+ * Factory method to create a new Timeout.
+ *
+ * @param timeoutMs timeout to use.
+ * @return returns a new Timeout.
+ */
+ public static Timeout create(long timeoutMs) {
+ return new Timeout(timeoutMs);
+ }
+
+ /**
+ * Create a new timeout.
+ *
+ * @param timeoutMs the time in milliseconds for this timeout to lapse.
+ */
+ private Timeout(long timeoutMs) {
+ this.timeoutMs = timeoutMs;
+ this.timeStarted = SystemClock.elapsedRealtime();
+ }
+
+ /** Will return true when the timeout period has lapsed. */
+ public boolean getIsExpired() {
+ return (SystemClock.elapsedRealtime() - timeStarted) > timeoutMs;
+ }
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackStatesTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackStatesTest.java
new file mode 100644
index 000000000000..4964aef8b8c9
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackStatesTest.java
@@ -0,0 +1,377 @@
+// 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureResult.Key;
+import android.hardware.camera2.TotalCaptureResult;
+import io.flutter.plugins.camera.CameraCaptureCallback.CameraCaptureStateListener;
+import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper;
+import io.flutter.plugins.camera.types.Timeout;
+import io.flutter.plugins.camera.utils.TestUtils;
+import java.util.HashMap;
+import java.util.Map;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.mockito.MockedStatic;
+
+public class CameraCaptureCallbackStatesTest extends TestCase {
+ private final Integer aeState;
+ private final Integer afState;
+ private final CameraState cameraState;
+ private final boolean isTimedOut;
+
+ private Runnable validate;
+
+ private CameraCaptureCallback cameraCaptureCallback;
+ private CameraCaptureStateListener mockCaptureStateListener;
+ private CameraCaptureSession mockCameraCaptureSession;
+ private CaptureRequest mockCaptureRequest;
+ private CaptureResult mockPartialCaptureResult;
+ private CaptureTimeoutsWrapper mockCaptureTimeouts;
+ private TotalCaptureResult mockTotalCaptureResult;
+ private MockedStatic