Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit a9513d5

Browse files
[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 <[email protected]> * Update packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java Co-authored-by: Maurits van Beusekom <[email protected]> * Update packages/camera/camera/CHANGELOG.md Co-authored-by: Maurits van Beusekom <[email protected]> * Update packages/camera/camera/pubspec.yaml Co-authored-by: Maurits van Beusekom <[email protected]> * Remove unnecessary import. * Updated unit tests Co-authored-by: Maurits van Beusekom <[email protected]> Co-authored-by: Maurits van Beusekom <[email protected]>
1 parent 622ba57 commit a9513d5

File tree

5 files changed

+173
-25
lines changed

5 files changed

+173
-25
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.6.3+1
2+
3+
* Fixes flash & torch modes not working on some Android devices.
4+
15
## 0.6.3
26

37
* Adds torch mode as a flash mode for Android and iOS implementations.

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

Lines changed: 157 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import android.graphics.SurfaceTexture;
1313
import android.hardware.camera2.CameraAccessException;
1414
import android.hardware.camera2.CameraCaptureSession;
15+
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
1516
import android.hardware.camera2.CameraCharacteristics;
1617
import android.hardware.camera2.CameraDevice;
1718
import android.hardware.camera2.CameraManager;
@@ -29,12 +30,15 @@
2930
import android.os.Build;
3031
import android.os.Build.VERSION;
3132
import android.os.Build.VERSION_CODES;
33+
import android.os.Handler;
34+
import android.os.Looper;
3235
import android.util.Size;
3336
import android.view.OrientationEventListener;
3437
import android.view.Surface;
3538
import androidx.annotation.NonNull;
3639
import io.flutter.plugin.common.EventChannel;
3740
import io.flutter.plugin.common.MethodChannel.Result;
41+
import io.flutter.plugins.camera.PictureCaptureRequest.State;
3842
import io.flutter.plugins.camera.media.MediaRecorderBuilder;
3943
import io.flutter.plugins.camera.types.FlashMode;
4044
import io.flutter.plugins.camera.types.ResolutionPreset;
@@ -246,7 +250,7 @@ public void takePicture(@NonNull final Result result) {
246250
},
247251
null);
248252

249-
runPicturePreCapture();
253+
runPictureAutoFocus();
250254
}
251255

252256
private final CameraCaptureSession.CaptureCallback pictureCaptureCallback =
@@ -256,18 +260,15 @@ public void onCaptureCompleted(
256260
@NonNull CameraCaptureSession session,
257261
@NonNull CaptureRequest request,
258262
@NonNull TotalCaptureResult result) {
259-
assert (pictureCaptureRequest != null);
260-
switch (pictureCaptureRequest.getState()) {
261-
case awaitingPreCapture:
262-
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
263-
// Some devices might return null here, in which case we will also continue.
264-
if (aeState == null
265-
|| aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
266-
|| aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
267-
runPictureCapture();
268-
}
269-
break;
270-
}
263+
processCapture(result);
264+
}
265+
266+
@Override
267+
public void onCaptureProgressed(
268+
@NonNull CameraCaptureSession session,
269+
@NonNull CaptureRequest request,
270+
@NonNull CaptureResult partialResult) {
271+
processCapture(partialResult);
271272
}
272273

273274
@Override
@@ -289,11 +290,54 @@ public void onCaptureFailed(
289290
}
290291
pictureCaptureRequest.error("captureFailure", reason, null);
291292
}
293+
294+
private void processCapture(CaptureResult result) {
295+
if (pictureCaptureRequest == null) {
296+
return;
297+
}
298+
299+
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
300+
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
301+
switch (pictureCaptureRequest.getState()) {
302+
case focusing:
303+
if (afState == null) {
304+
return;
305+
} else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
306+
|| afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
307+
// Some devices might return null here, in which case we will also continue.
308+
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
309+
runPictureCapture();
310+
} else {
311+
runPicturePreCapture();
312+
}
313+
}
314+
break;
315+
case preCapture:
316+
// Some devices might return null here, in which case we will also continue.
317+
if (aeState == null
318+
|| aeState == CaptureRequest.CONTROL_AE_STATE_PRECAPTURE
319+
|| aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
320+
|| aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
321+
pictureCaptureRequest.setState(State.waitingPreCaptureReady);
322+
}
323+
break;
324+
case waitingPreCaptureReady:
325+
if (aeState == null || aeState != CaptureRequest.CONTROL_AE_STATE_PRECAPTURE) {
326+
runPictureCapture();
327+
}
328+
}
329+
}
292330
};
293331

332+
private void runPictureAutoFocus() {
333+
assert (pictureCaptureRequest != null);
334+
pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing);
335+
lockAutoFocus();
336+
}
337+
294338
private void runPicturePreCapture() {
295339
assert (pictureCaptureRequest != null);
296-
pictureCaptureRequest.setState(PictureCaptureRequest.State.awaitingPreCapture);
340+
pictureCaptureRequest.setState(PictureCaptureRequest.State.preCapture);
297341

298342
captureRequestBuilder.set(
299343
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
@@ -331,7 +375,47 @@ private void runPictureCapture() {
331375
CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
332376
break;
333377
}
334-
cameraCaptureSession.capture(captureBuilder.build(), pictureCaptureCallback, null);
378+
cameraCaptureSession.stopRepeating();
379+
cameraCaptureSession.capture(
380+
captureBuilder.build(),
381+
new CameraCaptureSession.CaptureCallback() {
382+
@Override
383+
public void onCaptureCompleted(
384+
@NonNull CameraCaptureSession session,
385+
@NonNull CaptureRequest request,
386+
@NonNull TotalCaptureResult result) {
387+
unlockAutoFocus();
388+
}
389+
},
390+
null);
391+
} catch (CameraAccessException e) {
392+
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
393+
}
394+
}
395+
396+
private void lockAutoFocus() {
397+
captureRequestBuilder.set(
398+
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
399+
try {
400+
cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null);
401+
} catch (CameraAccessException e) {
402+
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
403+
}
404+
}
405+
406+
private void unlockAutoFocus() {
407+
captureRequestBuilder.set(
408+
CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
409+
initPreviewCaptureBuilder();
410+
try {
411+
cameraCaptureSession.capture(captureRequestBuilder.build(), null, null);
412+
} catch (CameraAccessException ignored) {
413+
}
414+
captureRequestBuilder.set(
415+
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
416+
try {
417+
cameraCaptureSession.setRepeatingRequest(
418+
captureRequestBuilder.build(), pictureCaptureCallback, null);
335419
} catch (CameraAccessException e) {
336420
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
337421
}
@@ -377,7 +461,10 @@ public void onConfigured(@NonNull CameraCaptureSession session) {
377461
}
378462
cameraCaptureSession = session;
379463
initPreviewCaptureBuilder();
380-
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
464+
cameraCaptureSession.setRepeatingRequest(
465+
captureRequestBuilder.build(),
466+
pictureCaptureCallback,
467+
new Handler(Looper.getMainLooper()));
381468
if (onSuccessCallback != null) {
382469
onSuccessCallback.run();
383470
}
@@ -531,11 +618,60 @@ public void setFlashMode(@NonNull final Result result, FlashMode mode)
531618
result.error("setFlashModeFailed", "Device does not have flash capabilities", null);
532619
return;
533620
}
621+
622+
// If switching directly from torch to auto or on, make sure we turn off the torch.
623+
if (flashMode == FlashMode.torch && mode != FlashMode.torch && mode != FlashMode.off) {
624+
this.flashMode = FlashMode.off;
625+
initPreviewCaptureBuilder();
626+
this.cameraCaptureSession.setRepeatingRequest(
627+
captureRequestBuilder.build(),
628+
new CaptureCallback() {
629+
private boolean isFinished = false;
630+
631+
@Override
632+
public void onCaptureCompleted(
633+
@NonNull CameraCaptureSession session,
634+
@NonNull CaptureRequest request,
635+
@NonNull TotalCaptureResult captureResult) {
636+
if (isFinished) {
637+
return;
638+
}
639+
640+
updateFlash(mode);
641+
result.success(null);
642+
isFinished = true;
643+
}
644+
645+
@Override
646+
public void onCaptureFailed(
647+
@NonNull CameraCaptureSession session,
648+
@NonNull CaptureRequest request,
649+
@NonNull CaptureFailure failure) {
650+
if (isFinished) {
651+
return;
652+
}
653+
654+
result.error("setFlashModeFailed", "Could not set flash mode.", null);
655+
isFinished = true;
656+
}
657+
},
658+
null);
659+
} else {
660+
updateFlash(mode);
661+
result.success(null);
662+
}
663+
}
664+
665+
private void updateFlash(FlashMode mode) {
534666
// Get flash
535-
this.flashMode = mode;
667+
flashMode = mode;
536668
initPreviewCaptureBuilder();
537-
this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
538-
result.success(null);
669+
try {
670+
cameraCaptureSession.setRepeatingRequest(
671+
captureRequestBuilder.build(), pictureCaptureCallback, null);
672+
} catch (CameraAccessException e) {
673+
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
674+
}
539675
}
540676

541677
private void initPreviewCaptureBuilder() {
@@ -563,6 +699,8 @@ private void initPreviewCaptureBuilder() {
563699
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
564700
break;
565701
}
702+
captureRequestBuilder.set(
703+
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
566704
}
567705

568706
public void startPreview() throws CameraAccessException {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ class PictureCaptureRequest {
77

88
enum State {
99
idle,
10-
awaitingPreCapture,
10+
focusing,
11+
preCapture,
12+
waitingPreCaptureReady,
1113
capturing,
1214
finished,
1315
error,

packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ public void state_is_idle_by_default() {
2020
@Test
2121
public void setState_sets_state() {
2222
PictureCaptureRequest req = new PictureCaptureRequest(null);
23-
req.setState(PictureCaptureRequest.State.awaitingPreCapture);
23+
req.setState(PictureCaptureRequest.State.focusing);
24+
assertEquals("State is focusing", req.getState(), PictureCaptureRequest.State.focusing);
25+
req.setState(PictureCaptureRequest.State.preCapture);
26+
assertEquals("State is preCapture", req.getState(), PictureCaptureRequest.State.preCapture);
27+
req.setState(PictureCaptureRequest.State.waitingPreCaptureReady);
2428
assertEquals(
25-
"State is awaitingPreCapture",
29+
"State is waitingPreCaptureReady",
2630
req.getState(),
27-
PictureCaptureRequest.State.awaitingPreCapture);
31+
PictureCaptureRequest.State.waitingPreCaptureReady);
2832
req.setState(PictureCaptureRequest.State.capturing);
2933
assertEquals(
3034
"State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing);
@@ -49,7 +53,7 @@ public void isFinished_is_true_When_state_is_finished_or_error() {
4953
// Test false states
5054
req.setState(PictureCaptureRequest.State.idle);
5155
assertFalse(req.isFinished());
52-
req.setState(PictureCaptureRequest.State.awaitingPreCapture);
56+
req.setState(PictureCaptureRequest.State.preCapture);
5357
assertFalse(req.isFinished());
5458
req.setState(PictureCaptureRequest.State.capturing);
5559
assertFalse(req.isFinished());

packages/camera/camera/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera
22
description: A Flutter plugin for getting information about and controlling the
33
camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
44
and streaming image buffers to dart.
5-
version: 0.6.3
5+
version: 0.6.3+1
66
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera
77

88
dependencies:

0 commit comments

Comments
 (0)