Skip to content

Commit

Permalink
[camera] Fix flash/torch not working on some Android devices. (flutte…
Browse files Browse the repository at this point in the history
…r#3367)

* Wait pre capture to finish

* Add autofocus stage to capture

* Fixed autofocus stage

* Make sure torch is turned off

* Format & structure improvements

* Update changelog and pubspec

* Update packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

* Update packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

* Update packages/camera/camera/CHANGELOG.md

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

* Update packages/camera/camera/pubspec.yaml

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

* Remove unnecessary import.

* Updated unit tests

Co-authored-by: Maurits van Beusekom <maurits@baseflow.com>
Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>
  • Loading branch information
3 people committed Dec 24, 2020
1 parent 622ba57 commit a9513d5
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 25 deletions.
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.6.3+1

* Fixes flash & torch modes not working on some Android devices.

## 0.6.3

* Adds torch mode as a flash mode for Android and iOS implementations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
Expand All @@ -29,12 +30,15 @@
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.Looper;
import android.util.Size;
import android.view.OrientationEventListener;
import android.view.Surface;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.camera.PictureCaptureRequest.State;
import io.flutter.plugins.camera.media.MediaRecorderBuilder;
import io.flutter.plugins.camera.types.FlashMode;
import io.flutter.plugins.camera.types.ResolutionPreset;
Expand Down Expand Up @@ -246,7 +250,7 @@ public void takePicture(@NonNull final Result result) {
},
null);

runPicturePreCapture();
runPictureAutoFocus();
}

private final CameraCaptureSession.CaptureCallback pictureCaptureCallback =
Expand All @@ -256,18 +260,15 @@ public void onCaptureCompleted(
@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
assert (pictureCaptureRequest != null);
switch (pictureCaptureRequest.getState()) {
case awaitingPreCapture:
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
// Some devices might return null here, in which case we will also continue.
if (aeState == null
|| aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
|| aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
runPictureCapture();
}
break;
}
processCapture(result);
}

@Override
public void onCaptureProgressed(
@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
processCapture(partialResult);
}

@Override
Expand All @@ -289,11 +290,54 @@ public void onCaptureFailed(
}
pictureCaptureRequest.error("captureFailure", reason, null);
}

private void processCapture(CaptureResult result) {
if (pictureCaptureRequest == null) {
return;
}

Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
switch (pictureCaptureRequest.getState()) {
case focusing:
if (afState == null) {
return;
} else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
|| afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
// Some devices might return null here, in which case we will also continue.
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
runPictureCapture();
} else {
runPicturePreCapture();
}
}
break;
case preCapture:
// Some devices might return null here, in which case we will also continue.
if (aeState == null
|| aeState == CaptureRequest.CONTROL_AE_STATE_PRECAPTURE
|| aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
|| aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
pictureCaptureRequest.setState(State.waitingPreCaptureReady);
}
break;
case waitingPreCaptureReady:
if (aeState == null || aeState != CaptureRequest.CONTROL_AE_STATE_PRECAPTURE) {
runPictureCapture();
}
}
}
};

private void runPictureAutoFocus() {
assert (pictureCaptureRequest != null);
pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing);
lockAutoFocus();
}

private void runPicturePreCapture() {
assert (pictureCaptureRequest != null);
pictureCaptureRequest.setState(PictureCaptureRequest.State.awaitingPreCapture);
pictureCaptureRequest.setState(PictureCaptureRequest.State.preCapture);

captureRequestBuilder.set(
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
Expand Down Expand Up @@ -331,7 +375,47 @@ private void runPictureCapture() {
CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
break;
}
cameraCaptureSession.capture(captureBuilder.build(), pictureCaptureCallback, null);
cameraCaptureSession.stopRepeating();
cameraCaptureSession.capture(
captureBuilder.build(),
new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(
@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
unlockAutoFocus();
}
},
null);
} catch (CameraAccessException e) {
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
}
}

private void lockAutoFocus() {
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
try {
cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null);
} catch (CameraAccessException e) {
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
}
}

private void unlockAutoFocus() {
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
initPreviewCaptureBuilder();
try {
cameraCaptureSession.capture(captureRequestBuilder.build(), null, null);
} catch (CameraAccessException ignored) {
}
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
try {
cameraCaptureSession.setRepeatingRequest(
captureRequestBuilder.build(), pictureCaptureCallback, null);
} catch (CameraAccessException e) {
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
}
Expand Down Expand Up @@ -377,7 +461,10 @@ public void onConfigured(@NonNull CameraCaptureSession session) {
}
cameraCaptureSession = session;
initPreviewCaptureBuilder();
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
cameraCaptureSession.setRepeatingRequest(
captureRequestBuilder.build(),
pictureCaptureCallback,
new Handler(Looper.getMainLooper()));
if (onSuccessCallback != null) {
onSuccessCallback.run();
}
Expand Down Expand Up @@ -531,11 +618,60 @@ public void setFlashMode(@NonNull final Result result, FlashMode mode)
result.error("setFlashModeFailed", "Device does not have flash capabilities", null);
return;
}

// If switching directly from torch to auto or on, make sure we turn off the torch.
if (flashMode == FlashMode.torch && mode != FlashMode.torch && mode != FlashMode.off) {
this.flashMode = FlashMode.off;
initPreviewCaptureBuilder();
this.cameraCaptureSession.setRepeatingRequest(
captureRequestBuilder.build(),
new CaptureCallback() {
private boolean isFinished = false;

@Override
public void onCaptureCompleted(
@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult captureResult) {
if (isFinished) {
return;
}

updateFlash(mode);
result.success(null);
isFinished = true;
}

@Override
public void onCaptureFailed(
@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureFailure failure) {
if (isFinished) {
return;
}

result.error("setFlashModeFailed", "Could not set flash mode.", null);
isFinished = true;
}
},
null);
} else {
updateFlash(mode);
result.success(null);
}
}

private void updateFlash(FlashMode mode) {
// Get flash
this.flashMode = mode;
flashMode = mode;
initPreviewCaptureBuilder();
this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
result.success(null);
try {
cameraCaptureSession.setRepeatingRequest(
captureRequestBuilder.build(), pictureCaptureCallback, null);
} catch (CameraAccessException e) {
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
}
}

private void initPreviewCaptureBuilder() {
Expand Down Expand Up @@ -563,6 +699,8 @@ private void initPreviewCaptureBuilder() {
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
break;
}
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
}

public void startPreview() throws CameraAccessException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ class PictureCaptureRequest {

enum State {
idle,
awaitingPreCapture,
focusing,
preCapture,
waitingPreCaptureReady,
capturing,
finished,
error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ public void state_is_idle_by_default() {
@Test
public void setState_sets_state() {
PictureCaptureRequest req = new PictureCaptureRequest(null);
req.setState(PictureCaptureRequest.State.awaitingPreCapture);
req.setState(PictureCaptureRequest.State.focusing);
assertEquals("State is focusing", req.getState(), PictureCaptureRequest.State.focusing);
req.setState(PictureCaptureRequest.State.preCapture);
assertEquals("State is preCapture", req.getState(), PictureCaptureRequest.State.preCapture);
req.setState(PictureCaptureRequest.State.waitingPreCaptureReady);
assertEquals(
"State is awaitingPreCapture",
"State is waitingPreCaptureReady",
req.getState(),
PictureCaptureRequest.State.awaitingPreCapture);
PictureCaptureRequest.State.waitingPreCaptureReady);
req.setState(PictureCaptureRequest.State.capturing);
assertEquals(
"State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing);
Expand All @@ -49,7 +53,7 @@ public void isFinished_is_true_When_state_is_finished_or_error() {
// Test false states
req.setState(PictureCaptureRequest.State.idle);
assertFalse(req.isFinished());
req.setState(PictureCaptureRequest.State.awaitingPreCapture);
req.setState(PictureCaptureRequest.State.preCapture);
assertFalse(req.isFinished());
req.setState(PictureCaptureRequest.State.capturing);
assertFalse(req.isFinished());
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.6.3
version: 0.6.3+1
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera

dependencies:
Expand Down

0 comments on commit a9513d5

Please sign in to comment.