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

Commit 5691d52

Browse files
authored
[camera] Fix for CameraAccessException that prevents image capture on certain devices running Android 7/8 (#4572)
1 parent 715fdbe commit 5691d52

File tree

4 files changed

+120
-5
lines changed

4 files changed

+120
-5
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
## 0.9.4+16
2+
3+
* Fixes a bug resulting in a `CameraAccessException` that prevents image
4+
capture on some Android devices.
5+
16
## 0.9.4+15
27

38
* Uses dispatch queue for pixel buffer synchronization on iOS.
4-
* Minor iOS internal code cleanup related to queue helper functions.
9+
* Minor iOS internal code cleanup related to queue helper functions.
510

611
## 0.9.4+14
712

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

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ interface ErrorCallback {
7979
void onError(String errorCode, String errorMessage);
8080
}
8181

82+
/** A mockable wrapper for CameraDevice calls. */
83+
interface CameraDeviceWrapper {
84+
@NonNull
85+
CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException;
86+
87+
@TargetApi(VERSION_CODES.P)
88+
void createCaptureSession(SessionConfiguration config) throws CameraAccessException;
89+
90+
@TargetApi(VERSION_CODES.LOLLIPOP)
91+
void createCaptureSession(
92+
@NonNull List<Surface> outputs,
93+
@NonNull CameraCaptureSession.StateCallback callback,
94+
@Nullable Handler handler)
95+
throws CameraAccessException;
96+
97+
void close();
98+
}
99+
82100
class Camera
83101
implements CameraCaptureCallback.CameraCaptureStateListener,
84102
ImageReader.OnImageAvailableListener {
@@ -114,7 +132,7 @@ class Camera
114132
/** An additional thread for running tasks that shouldn't block the UI. */
115133
private HandlerThread backgroundHandlerThread;
116134

117-
private CameraDevice cameraDevice;
135+
private CameraDeviceWrapper cameraDevice;
118136
private CameraCaptureSession captureSession;
119137
private ImageReader pictureImageReader;
120138
private ImageReader imageStreamReader;
@@ -136,6 +154,44 @@ class Camera
136154

137155
private MethodChannel.Result flutterResult;
138156

157+
/** A CameraDeviceWrapper implementation that forwards calls to a CameraDevice. */
158+
private class DefaultCameraDeviceWrapper implements CameraDeviceWrapper {
159+
private final CameraDevice cameraDevice;
160+
161+
private DefaultCameraDeviceWrapper(CameraDevice cameraDevice) {
162+
this.cameraDevice = cameraDevice;
163+
}
164+
165+
@NonNull
166+
@Override
167+
public CaptureRequest.Builder createCaptureRequest(int templateType)
168+
throws CameraAccessException {
169+
return cameraDevice.createCaptureRequest(templateType);
170+
}
171+
172+
@TargetApi(VERSION_CODES.P)
173+
@Override
174+
public void createCaptureSession(SessionConfiguration config) throws CameraAccessException {
175+
cameraDevice.createCaptureSession(config);
176+
}
177+
178+
@TargetApi(VERSION_CODES.LOLLIPOP)
179+
@SuppressWarnings("deprecation")
180+
@Override
181+
public void createCaptureSession(
182+
@NonNull List<Surface> outputs,
183+
@NonNull CameraCaptureSession.StateCallback callback,
184+
@Nullable Handler handler)
185+
throws CameraAccessException {
186+
cameraDevice.createCaptureSession(outputs, callback, backgroundHandler);
187+
}
188+
189+
@Override
190+
public void close() {
191+
cameraDevice.close();
192+
}
193+
}
194+
139195
public Camera(
140196
final Activity activity,
141197
final SurfaceTextureEntry flutterTexture,
@@ -261,7 +317,7 @@ public void open(String imageFormatGroup) throws CameraAccessException {
261317
new CameraDevice.StateCallback() {
262318
@Override
263319
public void onOpened(@NonNull CameraDevice device) {
264-
cameraDevice = device;
320+
cameraDevice = new DefaultCameraDeviceWrapper(device);
265321
try {
266322
startPreview();
267323
dartMessenger.sendCameraInitializedEvent(
@@ -584,7 +640,6 @@ public void onCaptureCompleted(
584640

585641
try {
586642
captureSession.stopRepeating();
587-
captureSession.abortCaptures();
588643
Log.i(TAG, "sending capture request");
589644
captureSession.capture(stillBuilder.build(), captureCallback, backgroundHandler);
590645
} catch (CameraAccessException e) {

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@
2222
import android.hardware.camera2.CameraCaptureSession;
2323
import android.hardware.camera2.CameraMetadata;
2424
import android.hardware.camera2.CaptureRequest;
25+
import android.hardware.camera2.params.SessionConfiguration;
26+
import android.media.ImageReader;
2527
import android.media.MediaRecorder;
2628
import android.os.Build;
2729
import android.os.Handler;
2830
import android.os.HandlerThread;
31+
import android.view.Surface;
2932
import androidx.annotation.NonNull;
33+
import androidx.annotation.Nullable;
3034
import androidx.lifecycle.LifecycleObserver;
3135
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
3236
import io.flutter.plugin.common.MethodChannel;
@@ -50,11 +54,39 @@
5054
import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
5155
import io.flutter.plugins.camera.utils.TestUtils;
5256
import io.flutter.view.TextureRegistry;
57+
import java.util.ArrayList;
58+
import java.util.List;
5359
import org.junit.After;
5460
import org.junit.Before;
5561
import org.junit.Test;
5662
import org.mockito.MockedStatic;
5763

64+
class FakeCameraDeviceWrapper implements CameraDeviceWrapper {
65+
final List<CaptureRequest.Builder> captureRequests;
66+
67+
FakeCameraDeviceWrapper(List<CaptureRequest.Builder> captureRequests) {
68+
this.captureRequests = captureRequests;
69+
}
70+
71+
@NonNull
72+
@Override
73+
public CaptureRequest.Builder createCaptureRequest(int var1) {
74+
return captureRequests.remove(0);
75+
}
76+
77+
@Override
78+
public void createCaptureSession(SessionConfiguration config) {}
79+
80+
@Override
81+
public void createCaptureSession(
82+
@NonNull List<Surface> outputs,
83+
@NonNull CameraCaptureSession.StateCallback callback,
84+
@Nullable Handler handler) {}
85+
86+
@Override
87+
public void close() {}
88+
}
89+
5890
public class CameraTest {
5991
private CameraProperties mockCameraProperties;
6092
private CameraFeatureFactory mockCameraFeatureFactory;
@@ -801,6 +833,29 @@ public void startBackgroundThread_shouldNotStartNewThreadWhenAlreadyCreated() {
801833
verify(mockHandlerThread, times(1)).start();
802834
}
803835

836+
@Test
837+
public void onConverge_shouldTakePictureWithoutAbortingSession() throws CameraAccessException {
838+
ArrayList<CaptureRequest.Builder> mockRequestBuilders = new ArrayList<>();
839+
mockRequestBuilders.add(mock(CaptureRequest.Builder.class));
840+
CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders);
841+
// Stub out other features used by the flow.
842+
TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera);
843+
TestUtils.setPrivateField(camera, "pictureImageReader", mock(ImageReader.class));
844+
SensorOrientationFeature mockSensorOrientationFeature =
845+
mockCameraFeatureFactory.createSensorOrientationFeature(mockCameraProperties, null, null);
846+
DeviceOrientationManager mockDeviceOrientationManager = mock(DeviceOrientationManager.class);
847+
when(mockSensorOrientationFeature.getDeviceOrientationManager())
848+
.thenReturn(mockDeviceOrientationManager);
849+
850+
// Simulate a post-precapture flow.
851+
camera.onConverged();
852+
// A picture should be taken.
853+
verify(mockCaptureSession, times(1)).capture(any(), any(), any());
854+
// The session shuold not be aborted as part of this flow, as this breaks capture on some
855+
// devices, and causes delays on others.
856+
verify(mockCaptureSession, never()).abortCaptures();
857+
}
858+
804859
private static class TestCameraFeatureFactory implements CameraFeatureFactory {
805860
private final AutoFocusFeature mockAutoFocusFeature;
806861
private final ExposureLockFeature mockExposureLockFeature;

packages/camera/camera/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
44
Dart.
55
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
7-
version: 0.9.4+15
7+
version: 0.9.4+16
88

99
environment:
1010
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)