diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index e9972aede28a..1bc9be5af9c3 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 0521c422d794..401963c91374 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 @@ 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; @@ -382,8 +384,8 @@ 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); } @@ -391,7 +393,7 @@ 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); @@ -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(); @@ -679,6 +685,7 @@ public void stopBackgroundThread() { } backgroundHandlerThread = null; backgroundHandler = null; + stoppingBackgroundHandlerThread = false; } /** Start capturing a picture, doing autofocus first. */ @@ -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; 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 167733b9dcca..b85b685ca90b 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; @@ -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 mockRequestBuilders = new ArrayList<>(); @@ -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 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 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 7d93ecb22e47..a1b7a930e228 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -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"