From b670a53df6106d79593268c93088d0f34b21dbb6 Mon Sep 17 00:00:00 2001 From: Bodhi Mulders Date: Thu, 24 Dec 2020 17:20:43 +0100 Subject: [PATCH] [camera] Fix flash/torch not working on some Android devices. (#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 * Update packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java Co-authored-by: Maurits van Beusekom * Update packages/camera/camera/CHANGELOG.md Co-authored-by: Maurits van Beusekom * Update packages/camera/camera/pubspec.yaml Co-authored-by: Maurits van Beusekom * Remove unnecessary import. * Updated unit tests Co-authored-by: Maurits van Beusekom Co-authored-by: Maurits van Beusekom --- packages/camera/camera/CHANGELOG.md | 5 + .../io/flutter/plugins/camera/Camera.java | 175 ++++++++++++++++-- .../plugins/camera/PictureCaptureRequest.java | 4 +- .../camera/PictureCaptureRequestTest.java | 12 +- packages/camera/camera/pubspec.yaml | 2 +- 5 files changed, 173 insertions(+), 25 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 2f77209b36ae..d7857ae3d727 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.6.3+1 + +* Fixes flash & torch modes not working on some Android devices. + +## 0.6.3 ## 0.6.2+1 diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index d3d5dac6205f..3a652a55f22d 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -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; @@ -32,6 +33,8 @@ import android.os.Build.VERSION_CODES; import android.util.Range; import android.util.Rational; +import android.os.Handler; +import android.os.Looper; import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; @@ -41,6 +44,7 @@ import io.flutter.plugins.camera.media.MediaRecorderBuilder; import io.flutter.plugins.camera.types.ExposureMode; import io.flutter.plugins.camera.types.IsoMode; +import io.flutter.plugins.camera.PictureCaptureRequest.State; import io.flutter.plugins.camera.types.WbMode; import io.flutter.plugins.camera.types.FlashMode; import io.flutter.plugins.camera.types.ResolutionPreset; @@ -266,7 +270,7 @@ public void takePicture(@NonNull final Result result) { }, null); - runPicturePreCapture(); + runPictureAutoFocus(); } private final CameraCaptureSession.CaptureCallback pictureCaptureCallback = @@ -276,18 +280,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 @@ -309,11 +310,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, @@ -351,7 +395,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); } @@ -397,7 +481,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(); } @@ -546,12 +633,49 @@ public void setFlashMode(@NonNull final Result result, FlashMode mode) result.error("setFlashModeFailed", "Device does not have flash capabilities", null); return; } - // Get flash - this.flashMode = mode; + + // 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(), null, null); + 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); } + } public void setWbMode(@NonNull final Result result, WbMode mode) throws CameraAccessException { @@ -741,6 +865,18 @@ public void setIsoValue(@NonNull final Result result, int value) result.success(value); } + private void updateFlash(FlashMode mode) { + // Get flash + flashMode = mode; + initPreviewCaptureBuilder(); + try { + cameraCaptureSession.setRepeatingRequest( + captureRequestBuilder.build(), pictureCaptureCallback, null); + } catch (CameraAccessException e) { + pictureCaptureRequest.error("cameraAccess", e.getMessage(), null); + } + } + private void initPreviewCaptureBuilder() { captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); // Applying flash modes @@ -767,6 +903,7 @@ private void initPreviewCaptureBuilder() { captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); break; } + // Applying auto exposure if (aeMeteringRectangle != null) { captureRequestBuilder.set( diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java index e365f071d9a8..1103b8583ad6 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java @@ -7,7 +7,9 @@ class PictureCaptureRequest { enum State { idle, - awaitingPreCapture, + focusing, + preCapture, + waitingPreCaptureReady, capturing, finished, error, diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java index 2b6aa0f25fcf..2356b306c6c4 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java @@ -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); @@ -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()); diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 6daa2407ed6b..e02838fb75b8 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -6,7 +6,7 @@ description: A Flutter plugin for getting information about and controlling the -version: 0.6.3 +version: 0.6.3+1 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: