Skip to content

Commit b670a53

Browse files
BeMacizedmvanbeusekom
authored andcommitted
[camera] Fix flash/torch not working on some Android devices. (flutter#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 aa9b4c5 commit b670a53

File tree

5 files changed

+173
-25
lines changed

5 files changed

+173
-25
lines changed

packages/camera/camera/CHANGELOG.md

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

27
## 0.6.2+1
38

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

Lines changed: 156 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;
@@ -32,6 +33,8 @@
3233
import android.os.Build.VERSION_CODES;
3334
import android.util.Range;
3435
import android.util.Rational;
36+
import android.os.Handler;
37+
import android.os.Looper;
3538
import android.util.Size;
3639
import android.view.OrientationEventListener;
3740
import android.view.Surface;
@@ -41,6 +44,7 @@
4144
import io.flutter.plugins.camera.media.MediaRecorderBuilder;
4245
import io.flutter.plugins.camera.types.ExposureMode;
4346
import io.flutter.plugins.camera.types.IsoMode;
47+
import io.flutter.plugins.camera.PictureCaptureRequest.State;
4448
import io.flutter.plugins.camera.types.WbMode;
4549
import io.flutter.plugins.camera.types.FlashMode;
4650
import io.flutter.plugins.camera.types.ResolutionPreset;
@@ -266,7 +270,7 @@ public void takePicture(@NonNull final Result result) {
266270
},
267271
null);
268272

269-
runPicturePreCapture();
273+
runPictureAutoFocus();
270274
}
271275

272276
private final CameraCaptureSession.CaptureCallback pictureCaptureCallback =
@@ -276,18 +280,15 @@ public void onCaptureCompleted(
276280
@NonNull CameraCaptureSession session,
277281
@NonNull CaptureRequest request,
278282
@NonNull TotalCaptureResult result) {
279-
assert (pictureCaptureRequest != null);
280-
switch (pictureCaptureRequest.getState()) {
281-
case awaitingPreCapture:
282-
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
283-
// Some devices might return null here, in which case we will also continue.
284-
if (aeState == null
285-
|| aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
286-
|| aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
287-
runPictureCapture();
288-
}
289-
break;
290-
}
283+
processCapture(result);
284+
}
285+
286+
@Override
287+
public void onCaptureProgressed(
288+
@NonNull CameraCaptureSession session,
289+
@NonNull CaptureRequest request,
290+
@NonNull CaptureResult partialResult) {
291+
processCapture(partialResult);
291292
}
292293

293294
@Override
@@ -309,11 +310,54 @@ public void onCaptureFailed(
309310
}
310311
pictureCaptureRequest.error("captureFailure", reason, null);
311312
}
313+
314+
private void processCapture(CaptureResult result) {
315+
if (pictureCaptureRequest == null) {
316+
return;
317+
}
318+
319+
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
320+
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
321+
switch (pictureCaptureRequest.getState()) {
322+
case focusing:
323+
if (afState == null) {
324+
return;
325+
} else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
326+
|| afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
327+
// Some devices might return null here, in which case we will also continue.
328+
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
329+
runPictureCapture();
330+
} else {
331+
runPicturePreCapture();
332+
}
333+
}
334+
break;
335+
case preCapture:
336+
// Some devices might return null here, in which case we will also continue.
337+
if (aeState == null
338+
|| aeState == CaptureRequest.CONTROL_AE_STATE_PRECAPTURE
339+
|| aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
340+
|| aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
341+
pictureCaptureRequest.setState(State.waitingPreCaptureReady);
342+
}
343+
break;
344+
case waitingPreCaptureReady:
345+
if (aeState == null || aeState != CaptureRequest.CONTROL_AE_STATE_PRECAPTURE) {
346+
runPictureCapture();
347+
}
348+
}
349+
}
312350
};
313351

352+
private void runPictureAutoFocus() {
353+
assert (pictureCaptureRequest != null);
354+
pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing);
355+
lockAutoFocus();
356+
}
357+
314358
private void runPicturePreCapture() {
315359
assert (pictureCaptureRequest != null);
316-
pictureCaptureRequest.setState(PictureCaptureRequest.State.awaitingPreCapture);
360+
pictureCaptureRequest.setState(PictureCaptureRequest.State.preCapture);
317361

318362
captureRequestBuilder.set(
319363
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
@@ -351,7 +395,47 @@ private void runPictureCapture() {
351395
CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
352396
break;
353397
}
354-
cameraCaptureSession.capture(captureBuilder.build(), pictureCaptureCallback, null);
398+
cameraCaptureSession.stopRepeating();
399+
cameraCaptureSession.capture(
400+
captureBuilder.build(),
401+
new CameraCaptureSession.CaptureCallback() {
402+
@Override
403+
public void onCaptureCompleted(
404+
@NonNull CameraCaptureSession session,
405+
@NonNull CaptureRequest request,
406+
@NonNull TotalCaptureResult result) {
407+
unlockAutoFocus();
408+
}
409+
},
410+
null);
411+
} catch (CameraAccessException e) {
412+
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
413+
}
414+
}
415+
416+
private void lockAutoFocus() {
417+
captureRequestBuilder.set(
418+
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
419+
try {
420+
cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null);
421+
} catch (CameraAccessException e) {
422+
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
423+
}
424+
}
425+
426+
private void unlockAutoFocus() {
427+
captureRequestBuilder.set(
428+
CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
429+
initPreviewCaptureBuilder();
430+
try {
431+
cameraCaptureSession.capture(captureRequestBuilder.build(), null, null);
432+
} catch (CameraAccessException ignored) {
433+
}
434+
captureRequestBuilder.set(
435+
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
436+
try {
437+
cameraCaptureSession.setRepeatingRequest(
438+
captureRequestBuilder.build(), pictureCaptureCallback, null);
355439
} catch (CameraAccessException e) {
356440
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
357441
}
@@ -397,7 +481,10 @@ public void onConfigured(@NonNull CameraCaptureSession session) {
397481
}
398482
cameraCaptureSession = session;
399483
initPreviewCaptureBuilder();
400-
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
484+
cameraCaptureSession.setRepeatingRequest(
485+
captureRequestBuilder.build(),
486+
pictureCaptureCallback,
487+
new Handler(Looper.getMainLooper()));
401488
if (onSuccessCallback != null) {
402489
onSuccessCallback.run();
403490
}
@@ -546,12 +633,49 @@ public void setFlashMode(@NonNull final Result result, FlashMode mode)
546633
result.error("setFlashModeFailed", "Device does not have flash capabilities", null);
547634
return;
548635
}
549-
// Get flash
550-
this.flashMode = mode;
636+
637+
// If switching directly from torch to auto or on, make sure we turn off the torch.
638+
if (flashMode == FlashMode.torch && mode != FlashMode.torch && mode != FlashMode.off) {
639+
this.flashMode = FlashMode.off;
551640
initPreviewCaptureBuilder();
552-
this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
641+
this.cameraCaptureSession.setRepeatingRequest(
642+
captureRequestBuilder.build(),
643+
new CaptureCallback() {
644+
private boolean isFinished = false;
645+
646+
@Override
647+
public void onCaptureCompleted(
648+
@NonNull CameraCaptureSession session,
649+
@NonNull CaptureRequest request,
650+
@NonNull TotalCaptureResult captureResult) {
651+
if (isFinished) {
652+
return;
653+
}
654+
655+
updateFlash(mode);
656+
result.success(null);
657+
isFinished = true;
658+
}
659+
660+
@Override
661+
public void onCaptureFailed(
662+
@NonNull CameraCaptureSession session,
663+
@NonNull CaptureRequest request,
664+
@NonNull CaptureFailure failure) {
665+
if (isFinished) {
666+
return;
667+
}
668+
669+
result.error("setFlashModeFailed", "Could not set flash mode.", null);
670+
isFinished = true;
671+
}
672+
},
673+
null);
674+
} else {
675+
updateFlash(mode);
553676
result.success(null);
554677
}
678+
}
555679

556680
public void setWbMode(@NonNull final Result result, WbMode mode)
557681
throws CameraAccessException {
@@ -741,6 +865,18 @@ public void setIsoValue(@NonNull final Result result, int value)
741865
result.success(value);
742866
}
743867

868+
private void updateFlash(FlashMode mode) {
869+
// Get flash
870+
flashMode = mode;
871+
initPreviewCaptureBuilder();
872+
try {
873+
cameraCaptureSession.setRepeatingRequest(
874+
captureRequestBuilder.build(), pictureCaptureCallback, null);
875+
} catch (CameraAccessException e) {
876+
pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
877+
}
878+
}
879+
744880
private void initPreviewCaptureBuilder() {
745881
captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
746882
// Applying flash modes
@@ -767,6 +903,7 @@ private void initPreviewCaptureBuilder() {
767903
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
768904
break;
769905
}
906+
770907
// Applying auto exposure
771908
if (aeMeteringRectangle != null) {
772909
captureRequestBuilder.set(

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
@@ -6,7 +6,7 @@ description: A Flutter plugin for getting information about and controlling the
66

77

88

9-
version: 0.6.3
9+
version: 0.6.3+1
1010
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera
1111
dependencies:
1212
flutter:

0 commit comments

Comments
 (0)