Skip to content

Commit

Permalink
[camera] Partially Address CameraAccessException: CAMERA_ERROR (flutt…
Browse files Browse the repository at this point in the history
  • Loading branch information
camsim99 authored and mauricioluz committed Jan 26, 2023
1 parent a833e4e commit 6eaf6c6
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 5 deletions.
4 changes: 4 additions & 0 deletions packages/camera/camera_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ class Camera

/** 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;
Expand Down Expand Up @@ -382,16 +384,16 @@ public void onError(@NonNull CameraDevice cameraDevice, int errorCode) {
backgroundHandler);
}

private void createCaptureSession(int templateType, Surface... surfaces)
throws CameraAccessException {
@VisibleForTesting
void createCaptureSession(int templateType, Surface... surfaces) throws CameraAccessException {
createCaptureSession(templateType, null, surfaces);
}

private void createCaptureSession(
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);
Expand Down Expand Up @@ -669,7 +671,11 @@ public void startBackgroundThread() {

/** Stops the background thread and its {@link Handler}. */
public void stopBackgroundThread() {
if (stoppingBackgroundHandlerThread) {
return;
}
if (backgroundHandlerThread != null) {
stoppingBackgroundHandlerThread = true;
backgroundHandlerThread.quitSafely();
try {
backgroundHandlerThread.join();
Expand All @@ -679,6 +685,7 @@ public void stopBackgroundThread() {
}
backgroundHandlerThread = null;
backgroundHandler = null;
stoppingBackgroundHandlerThread = false;
}

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

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,13 +30,15 @@
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;
import androidx.lifecycle.LifecycleObserver;
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;
Expand Down Expand Up @@ -833,6 +837,28 @@ public void startBackgroundThread_shouldNotStartNewThreadWhenAlreadyCreated() {
verify(mockHandlerThread, times(1)).start();
}

@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<>();
Expand All @@ -856,6 +882,52 @@ public void onConverge_shouldTakePictureWithoutAbortingSession() throws CameraAc
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;
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_android
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"
Expand Down

0 comments on commit 6eaf6c6

Please sign in to comment.