Skip to content

Commit

Permalink
Merge pull request flutter#5 from bottlepay/android-rework-extract-ca…
Browse files Browse the repository at this point in the history
…pture-callback

Android rework extract capture callback
  • Loading branch information
acoutts authored Mar 6, 2021
2 parents 25cff82 + e7808a5 commit b55f2e5
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 219 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ interface ErrorCallback {
void onError(String errorCode, String errorMessage);
}

public class Camera {
class Camera implements CameraCaptureCallback.CameraCaptureStateListener {
private static final String TAG = "Camera";

/** Conversion from screen rotation to JPEG orientation. */
Expand All @@ -92,7 +92,6 @@ public class Camera {

private final SurfaceTextureEntry flutterTexture;
private final DeviceOrientationManager deviceOrientationListener;
private final boolean isFrontFacing;
private final int sensorOrientation;
private final Size captureSize;
private final Size previewSize;
Expand Down Expand Up @@ -166,88 +165,7 @@ public void onImageAvailable(ImageReader reader) {
private CameraRegions cameraRegions;
private int exposureOffset;
/** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */
private final CameraCaptureSession.CaptureCallback mCaptureCallback =
new CameraCaptureSession.CaptureCallback() {

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.i(TAG, "mCaptureCallback | 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 == CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN
|| afState == CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED
|| afState == CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
// CONTROL_AE_STATE can be null on some devices

if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
takePictureAfterPrecapture();
} else {
runPrecaptureSequence();
}
}
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) {
cameraState = CameraState.STATE_WAITING_PRECAPTURE_DONE;
pictureCaptureRequest.setState(
PictureCaptureRequestState.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) {
takePictureAfterPrecapture();
} else {
if (pictureCaptureRequest.hitPreCaptureTimeout()) {
// Log.i(TAG, "===> Hit precapture timeout");
unlockAutoFocus();
}
}
break;
}
}
}

@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);
}
};
private final CameraCaptureCallback mCaptureCallback;

private Range<Integer> fpsRange;
private PlatformChannel.DeviceOrientation lockedCaptureOrientation;
Expand All @@ -259,24 +177,6 @@ public Camera(
final CameraProperties cameraProperties,
final ResolutionPreset resolutionPreset,
final boolean enableAudio) {
this(
activity,
flutterTexture,
dartMessenger,
cameraProperties,
resolutionPreset,
enableAudio,
null);
}

public Camera(
final Activity activity,
final SurfaceTextureEntry flutterTexture,
final DartMessenger dartMessenger,
final CameraProperties cameraProperties,
final ResolutionPreset resolutionPreset,
final boolean enableAudio,
@Nullable final DeviceOrientationManager deviceOrientationManager) {

if (activity == null) {
throw new IllegalStateException("No activity available!");
Expand All @@ -293,19 +193,18 @@ public Camera(
this.currentFocusMode = FocusMode.auto;
this.exposureOffset = 0;

mCaptureCallback = CameraCaptureCallback.create(this);

// Get camera characteristics and check for supported features
getAvailableFpsRange(cameraProperties);
mAutoFocusSupported = checkAutoFocusSupported(cameraProperties);
checkFlashSupported();

// Setup orientation
sensorOrientation = cameraProperties.getSensorOrientation();
isFrontFacing = cameraProperties.getLensFacing() == CameraMetadata.LENS_FACING_FRONT;
boolean isFrontFacing = cameraProperties.getLensFacing() == CameraMetadata.LENS_FACING_FRONT;

deviceOrientationListener =
deviceOrientationManager != null
? deviceOrientationManager
: new DeviceOrientationManager(
deviceOrientationListener = DeviceOrientationManager.create(
activity, dartMessenger, isFrontFacing, sensorOrientation);
deviceOrientationListener.start();

Expand All @@ -330,6 +229,21 @@ public Camera(
startBackgroundThread();
}

@Override
public void onConverged() {
takePictureAfterPrecapture();
}

@Override
public void onPrecapture() {
runPrecaptureSequence();
}

@Override
public void onPrecaptureTimeout() {
unlockAutoFocus();
}

/** Get the current camera state (use for testing). */
public CameraState getState() {
return this.cameraState;
Expand Down Expand Up @@ -642,7 +556,8 @@ public void takePicture(@NonNull final Result result) {
final File file = File.createTempFile("CAP", ".jpg", outputDir);

// Start a new capture
pictureCaptureRequest = new PictureCaptureRequest(result, file, dartMessenger);
pictureCaptureRequest = PictureCaptureRequest.create(result, file, dartMessenger);
mCaptureCallback.setPictureCaptureRequest(pictureCaptureRequest);
} catch (IOException | SecurityException e) {
pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null);
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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 androidx.annotation.NonNull;

class CameraCaptureCallback extends CaptureCallback {
interface CameraCaptureStateListener {
void onConverged();
void onPrecapture();
void onPrecaptureTimeout();
}

private final CameraCaptureStateListener cameraStateListener;

private CameraState cameraState;
private PictureCaptureRequest pictureCaptureRequest;

public static CameraCaptureCallback create(
@NonNull CameraCaptureStateListener cameraStateListener) {
return new CameraCaptureCallback(cameraStateListener);
}

private CameraCaptureCallback(
@NonNull CameraCaptureStateListener cameraStateListener) {
cameraState = CameraState.STATE_PREVIEW;
this.cameraStateListener = cameraStateListener;
this.pictureCaptureRequest = pictureCaptureRequest;
}

public CameraState getCameraState() {
return cameraState;
}

public void setCameraState(@NonNull CameraState state) {
cameraState = state;

if (pictureCaptureRequest != null && state == CameraState.STATE_WAITING_PRECAPTURE_DONE) {
pictureCaptureRequest.setState(
PictureCaptureRequestState.STATE_WAITING_PRECAPTURE_DONE);
}
}

public void setPictureCaptureRequest(@NonNull PictureCaptureRequest pictureCaptureRequest) {
this.pictureCaptureRequest = pictureCaptureRequest;
}

private void process(CaptureResult result) {
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);

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 == CaptureRequest.CONTROL_AF_STATE_PASSIVE_SCAN
|| afState == CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED
|| afState == CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
// CONTROL_AE_STATE can be null on some devices

if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
cameraStateListener.onConverged();
} else {
cameraStateListener.onPrecapture();
}
}
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);
}
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 (pictureCaptureRequest != null && pictureCaptureRequest.hitPreCaptureTimeout()) {
// Log.i(TAG, "===> Hit precapture timeout");
cameraStateListener.onPrecaptureTimeout();
}
break;
}
}
}

@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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ enum CameraEventType {
}
}

DartMessenger(BinaryMessenger messenger, long cameraId) {
public DartMessenger(BinaryMessenger messenger, long cameraId) {
cameraChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/camera" + cameraId);
deviceChannel = new MethodChannel(messenger, "flutter.io/cameraPlugin/device");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@ class DeviceOrientationManager {
private OrientationEventListener orientationEventListener;
private BroadcastReceiver broadcastReceiver;

public DeviceOrientationManager(
/**
* Factory method to create a device orientation manager.
*/
public static DeviceOrientationManager create(
Activity activity,
DartMessenger messenger,
boolean isFrontFacing,
int sensorOrientation
) {
return new DeviceOrientationManager(activity, messenger, isFrontFacing, sensorOrientation);
}

private DeviceOrientationManager(
Activity activity, DartMessenger messenger, boolean isFrontFacing, int sensorOrientation) {
this.activity = activity;
this.messenger = messenger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,33 +51,33 @@ class PictureCaptureRequest {
};

/**
* Constructor to create a picture capture request.
* Factory method to create a picture capture request.
*
* @param result
* @param file
*/
public PictureCaptureRequest(
static PictureCaptureRequest create(
MethodChannel.Result result,
File file,
DartMessenger dartMessenger) {
this(
result,
file,
dartMessenger,
new TimeoutHandler()
);
return new PictureCaptureRequest(result, file, dartMessenger);
}

/** Constructor for unit tests where we can mock the timeout handler */
public PictureCaptureRequest(
/**
* Private constructor to create a picture capture request.
*
* @param result
* @param file
*/
private PictureCaptureRequest(
MethodChannel.Result result,
File file,
DartMessenger dartMessenger,
TimeoutHandler timeoutHandler) {
DartMessenger dartMessenger) {

this.result = result;
this.timeoutHandler = timeoutHandler;
this.file = file;
this.dartMessenger = dartMessenger;
this.timeoutHandler = TimeoutHandler.create();
}

/**
Expand Down Expand Up @@ -184,7 +184,11 @@ static class TimeoutHandler {
private static final int REQUEST_TIMEOUT = 5000;
private final Handler handler;

TimeoutHandler() {
public static TimeoutHandler create() {
return new TimeoutHandler();
}

private TimeoutHandler() {
this.handler = new Handler(Looper.getMainLooper());
}

Expand Down
Loading

0 comments on commit b55f2e5

Please sign in to comment.