Skip to content

Commit 8bf46f1

Browse files
authored
[camera] Partially Address CameraAccessException: CAMERA_ERROR (flutter#5723)
1 parent f8f2bd1 commit 8bf46f1

File tree

4 files changed

+95
-5
lines changed

4 files changed

+95
-5
lines changed

packages/camera/camera_android/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.9.8+3
2+
3+
* Skips duplicate calls to stop background thread and removes unnecessary closings of camera capture sessions on Android.
4+
15
## 0.9.8+2
26

37
* Fixes exception in registerWith caused by the switch to an in-package method channel.

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ class Camera
131131

132132
/** An additional thread for running tasks that shouldn't block the UI. */
133133
private HandlerThread backgroundHandlerThread;
134+
/** True when backgroundHandlerThread is in the process of being stopped. */
135+
private boolean stoppingBackgroundHandlerThread = false;
134136

135137
private CameraDeviceWrapper cameraDevice;
136138
private CameraCaptureSession captureSession;
@@ -382,16 +384,16 @@ public void onError(@NonNull CameraDevice cameraDevice, int errorCode) {
382384
backgroundHandler);
383385
}
384386

385-
private void createCaptureSession(int templateType, Surface... surfaces)
386-
throws CameraAccessException {
387+
@VisibleForTesting
388+
void createCaptureSession(int templateType, Surface... surfaces) throws CameraAccessException {
387389
createCaptureSession(templateType, null, surfaces);
388390
}
389391

390392
private void createCaptureSession(
391393
int templateType, Runnable onSuccessCallback, Surface... surfaces)
392394
throws CameraAccessException {
393395
// Close any existing capture session.
394-
closeCaptureSession();
396+
captureSession = null;
395397

396398
// Create a new capture builder.
397399
previewRequestBuilder = cameraDevice.createCaptureRequest(templateType);
@@ -669,7 +671,11 @@ public void startBackgroundThread() {
669671

670672
/** Stops the background thread and its {@link Handler}. */
671673
public void stopBackgroundThread() {
674+
if (stoppingBackgroundHandlerThread) {
675+
return;
676+
}
672677
if (backgroundHandlerThread != null) {
678+
stoppingBackgroundHandlerThread = true;
673679
backgroundHandlerThread.quitSafely();
674680
try {
675681
backgroundHandlerThread.join();
@@ -679,6 +685,7 @@ public void stopBackgroundThread() {
679685
}
680686
backgroundHandlerThread = null;
681687
backgroundHandler = null;
688+
stoppingBackgroundHandlerThread = false;
682689
}
683690

684691
/** Start capturing a picture, doing autofocus first. */
@@ -1173,12 +1180,19 @@ private void closeCaptureSession() {
11731180

11741181
public void close() {
11751182
Log.i(TAG, "close");
1176-
closeCaptureSession();
11771183

11781184
if (cameraDevice != null) {
11791185
cameraDevice.close();
11801186
cameraDevice = null;
1187+
1188+
// Closing the CameraDevice without closing the CameraCaptureSession is recommended
1189+
// for quickly closing the camera:
1190+
// https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close()
1191+
captureSession = null;
1192+
} else {
1193+
closeCaptureSession();
11811194
}
1195+
11821196
if (pictureImageReader != null) {
11831197
pictureImageReader.close();
11841198
pictureImageReader = null;

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

+72
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import static org.mockito.Mockito.when;
1919

2020
import android.app.Activity;
21+
import android.graphics.SurfaceTexture;
2122
import android.hardware.camera2.CameraAccessException;
2223
import android.hardware.camera2.CameraCaptureSession;
24+
import android.hardware.camera2.CameraDevice;
2325
import android.hardware.camera2.CameraMetadata;
2426
import android.hardware.camera2.CaptureRequest;
2527
import android.hardware.camera2.params.SessionConfiguration;
@@ -28,13 +30,15 @@
2830
import android.os.Build;
2931
import android.os.Handler;
3032
import android.os.HandlerThread;
33+
import android.util.Size;
3134
import android.view.Surface;
3235
import androidx.annotation.NonNull;
3336
import androidx.annotation.Nullable;
3437
import androidx.lifecycle.LifecycleObserver;
3538
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
3639
import io.flutter.plugin.common.MethodChannel;
3740
import io.flutter.plugins.camera.features.CameraFeatureFactory;
41+
import io.flutter.plugins.camera.features.CameraFeatures;
3842
import io.flutter.plugins.camera.features.Point;
3943
import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
4044
import io.flutter.plugins.camera.features.autofocus.FocusMode;
@@ -833,6 +837,28 @@ public void startBackgroundThread_shouldNotStartNewThreadWhenAlreadyCreated() {
833837
verify(mockHandlerThread, times(1)).start();
834838
}
835839

840+
@Test
841+
public void stopBackgroundThread_cancelsDuplicateCalls() throws InterruptedException {
842+
TestUtils.setPrivateField(camera, "stoppingBackgroundHandlerThread", true);
843+
844+
camera.startBackgroundThread();
845+
camera.stopBackgroundThread();
846+
847+
verify(mockHandlerThread, never()).quitSafely();
848+
verify(mockHandlerThread, never()).join();
849+
}
850+
851+
@Test
852+
public void stopBackgroundThread_proceedsWithoutDuplicateCall() throws InterruptedException {
853+
TestUtils.setPrivateField(camera, "stoppingBackgroundHandlerThread", false);
854+
855+
camera.startBackgroundThread();
856+
camera.stopBackgroundThread();
857+
858+
verify(mockHandlerThread).quitSafely();
859+
verify(mockHandlerThread).join();
860+
}
861+
836862
@Test
837863
public void onConverge_shouldTakePictureWithoutAbortingSession() throws CameraAccessException {
838864
ArrayList<CaptureRequest.Builder> mockRequestBuilders = new ArrayList<>();
@@ -856,6 +882,52 @@ public void onConverge_shouldTakePictureWithoutAbortingSession() throws CameraAc
856882
verify(mockCaptureSession, never()).abortCaptures();
857883
}
858884

885+
@Test
886+
public void createCaptureSession_doesNotCloseCaptureSession() throws CameraAccessException {
887+
Surface mockSurface = mock(Surface.class);
888+
SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class);
889+
ResolutionFeature mockResolutionFeature = mock(ResolutionFeature.class);
890+
Size mockSize = mock(Size.class);
891+
ArrayList<CaptureRequest.Builder> mockRequestBuilders = new ArrayList<>();
892+
mockRequestBuilders.add(mock(CaptureRequest.Builder.class));
893+
CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders);
894+
TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera);
895+
896+
TextureRegistry.SurfaceTextureEntry cameraFlutterTexture =
897+
(TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture");
898+
CameraFeatures cameraFeatures =
899+
(CameraFeatures) TestUtils.getPrivateField(camera, "cameraFeatures");
900+
ResolutionFeature resolutionFeature =
901+
(ResolutionFeature)
902+
TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature");
903+
904+
when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture);
905+
when(resolutionFeature.getPreviewSize()).thenReturn(mockSize);
906+
907+
camera.createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, mockSurface);
908+
909+
verify(mockCaptureSession, never()).close();
910+
}
911+
912+
@Test
913+
public void close_doesCloseCaptureSessionWhenCameraDeviceNull() {
914+
camera.close();
915+
916+
verify(mockCaptureSession).close();
917+
}
918+
919+
@Test
920+
public void close_doesNotCloseCaptureSessionWhenCameraDeviceNonNull() {
921+
ArrayList<CaptureRequest.Builder> mockRequestBuilders = new ArrayList<>();
922+
mockRequestBuilders.add(mock(CaptureRequest.Builder.class));
923+
CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders);
924+
TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera);
925+
926+
camera.close();
927+
928+
verify(mockCaptureSession, never()).close();
929+
}
930+
859931
private static class TestCameraFeatureFactory implements CameraFeatureFactory {
860932
private final AutoFocusFeature mockAutoFocusFeature;
861933
private final ExposureLockFeature mockExposureLockFeature;

packages/camera/camera_android/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_android
22
description: Android implementation of the camera plugin.
33
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.9.8+2
5+
version: 0.9.8+3
66

77
environment:
88
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)