[camera] Partially Address CameraAccessException: CAMERA_ERROR (#5723)
diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md
index e9972ae..1bc9be5 100644
--- a/packages/camera/camera_android/CHANGELOG.md
+++ b/packages/camera/camera_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.9.8+3
+
+* Skips duplicate calls to stop background thread and removes unnecessary closings of camera capture sessions on Android.
+
## 0.9.8+2
* Fixes exception in registerWith caused by the switch to an in-package method channel.
diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java
index 0521c42..401963c 100644
--- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java
+++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java
@@ -131,6 +131,8 @@
/** An additional thread for running tasks that shouldn't block the UI. */
private HandlerThread backgroundHandlerThread;
+ /** True when backgroundHandlerThread is in the process of being stopped. */
+ private boolean stoppingBackgroundHandlerThread = false;
private CameraDeviceWrapper cameraDevice;
private CameraCaptureSession captureSession;
@@ -382,8 +384,8 @@
backgroundHandler);
}
- private void createCaptureSession(int templateType, Surface... surfaces)
- throws CameraAccessException {
+ @VisibleForTesting
+ void createCaptureSession(int templateType, Surface... surfaces) throws CameraAccessException {
createCaptureSession(templateType, null, surfaces);
}
@@ -391,7 +393,7 @@
int templateType, Runnable onSuccessCallback, Surface... surfaces)
throws CameraAccessException {
// Close any existing capture session.
- closeCaptureSession();
+ captureSession = null;
// Create a new capture builder.
previewRequestBuilder = cameraDevice.createCaptureRequest(templateType);
@@ -669,7 +671,11 @@
/** Stops the background thread and its {@link Handler}. */
public void stopBackgroundThread() {
+ if (stoppingBackgroundHandlerThread) {
+ return;
+ }
if (backgroundHandlerThread != null) {
+ stoppingBackgroundHandlerThread = true;
backgroundHandlerThread.quitSafely();
try {
backgroundHandlerThread.join();
@@ -679,6 +685,7 @@
}
backgroundHandlerThread = null;
backgroundHandler = null;
+ stoppingBackgroundHandlerThread = false;
}
/** Start capturing a picture, doing autofocus first. */
@@ -1173,12 +1180,19 @@
public void close() {
Log.i(TAG, "close");
- closeCaptureSession();
if (cameraDevice != null) {
cameraDevice.close();
cameraDevice = null;
+
+ // Closing the CameraDevice without closing the CameraCaptureSession is recommended
+ // for quickly closing the camera:
+ // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close()
+ captureSession = null;
+ } else {
+ closeCaptureSession();
}
+
if (pictureImageReader != null) {
pictureImageReader.close();
pictureImageReader = null;
diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java
index 167733b..b85b685 100644
--- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java
+++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java
@@ -18,8 +18,10 @@
import static org.mockito.Mockito.when;
import android.app.Activity;
+import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.SessionConfiguration;
@@ -28,6 +30,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
+import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -35,6 +38,7 @@
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.camera.features.CameraFeatureFactory;
+import io.flutter.plugins.camera.features.CameraFeatures;
import io.flutter.plugins.camera.features.Point;
import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
import io.flutter.plugins.camera.features.autofocus.FocusMode;
@@ -834,6 +838,28 @@
}
@Test
+ public void stopBackgroundThread_cancelsDuplicateCalls() throws InterruptedException {
+ TestUtils.setPrivateField(camera, "stoppingBackgroundHandlerThread", true);
+
+ camera.startBackgroundThread();
+ camera.stopBackgroundThread();
+
+ verify(mockHandlerThread, never()).quitSafely();
+ verify(mockHandlerThread, never()).join();
+ }
+
+ @Test
+ public void stopBackgroundThread_proceedsWithoutDuplicateCall() throws InterruptedException {
+ TestUtils.setPrivateField(camera, "stoppingBackgroundHandlerThread", false);
+
+ camera.startBackgroundThread();
+ camera.stopBackgroundThread();
+
+ verify(mockHandlerThread).quitSafely();
+ verify(mockHandlerThread).join();
+ }
+
+ @Test
public void onConverge_shouldTakePictureWithoutAbortingSession() throws CameraAccessException {
ArrayList<CaptureRequest.Builder> mockRequestBuilders = new ArrayList<>();
mockRequestBuilders.add(mock(CaptureRequest.Builder.class));
@@ -856,6 +882,52 @@
verify(mockCaptureSession, never()).abortCaptures();
}
+ @Test
+ public void createCaptureSession_doesNotCloseCaptureSession() throws CameraAccessException {
+ Surface mockSurface = mock(Surface.class);
+ SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class);
+ ResolutionFeature mockResolutionFeature = mock(ResolutionFeature.class);
+ Size mockSize = mock(Size.class);
+ ArrayList<CaptureRequest.Builder> mockRequestBuilders = new ArrayList<>();
+ mockRequestBuilders.add(mock(CaptureRequest.Builder.class));
+ CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders);
+ TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera);
+
+ TextureRegistry.SurfaceTextureEntry cameraFlutterTexture =
+ (TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture");
+ CameraFeatures cameraFeatures =
+ (CameraFeatures) TestUtils.getPrivateField(camera, "cameraFeatures");
+ ResolutionFeature resolutionFeature =
+ (ResolutionFeature)
+ TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature");
+
+ when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture);
+ when(resolutionFeature.getPreviewSize()).thenReturn(mockSize);
+
+ camera.createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, mockSurface);
+
+ verify(mockCaptureSession, never()).close();
+ }
+
+ @Test
+ public void close_doesCloseCaptureSessionWhenCameraDeviceNull() {
+ camera.close();
+
+ verify(mockCaptureSession).close();
+ }
+
+ @Test
+ public void close_doesNotCloseCaptureSessionWhenCameraDeviceNonNull() {
+ ArrayList<CaptureRequest.Builder> mockRequestBuilders = new ArrayList<>();
+ mockRequestBuilders.add(mock(CaptureRequest.Builder.class));
+ CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders);
+ TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera);
+
+ camera.close();
+
+ verify(mockCaptureSession, never()).close();
+ }
+
private static class TestCameraFeatureFactory implements CameraFeatureFactory {
private final AutoFocusFeature mockAutoFocusFeature;
private final ExposureLockFeature mockExposureLockFeature;
diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml
index 7d93ecb..a1b7a93 100644
--- a/packages/camera/camera_android/pubspec.yaml
+++ b/packages/camera/camera_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: Android implementation of the camera plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.9.8+2
+version: 0.9.8+3
environment:
sdk: ">=2.14.0 <3.0.0"