Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
## NEXT
## 0.9.4+12

* Fixes bug resulting in a `CameraAccessException` that prevents image capture on some devices.
* Skips unnecessary AppDelegate setup for unit tests on iOS.

## 0.9.4+11

* Manages iOS camera's orientation-related states on a background queue to prevent potential race conditions.
* Manages iOS camera's orientation-related states on a background queue to prevent potential race conditions.

## 0.9.4+10

* iOS performance improvement by moving file writing from the main queue to a background IO queue.
* iOS performance improvement by moving file writing from the main queue to a background IO queue.

## 0.9.4+9

* iOS performance improvement by moving sample buffer handling from the main queue to a background session queue.
* Minor iOS internal code cleanup related to camera class and its delegate.
* iOS performance improvement by moving sample buffer handling from the main queue to a background session queue.
* Minor iOS internal code cleanup related to camera class and its delegate.
* Minor iOS internal code cleanup related to resolution preset, video format, focus mode, exposure mode and device orientation.
* Minor iOS internal code cleanup related to flash mode.

Expand All @@ -23,12 +24,12 @@

## 0.9.4+7

* Fixes a crash in iOS when passing null queue pointer into AVFoundation API due to race condition.
* Fixes a crash in iOS when passing null queue pointer into AVFoundation API due to race condition.
* Minor iOS internal code cleanup related to dispatch queue.

## 0.9.4+6

* Fixes a crash in iOS when using image stream due to calling Flutter engine API on non-main thread.
* Fixes a crash in iOS when using image stream due to calling Flutter engine API on non-main thread.

## 0.9.4+5

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ interface ErrorCallback {
void onError(String errorCode, String errorMessage);
}

/** A mockable wrapper for CameraDevice calls. */
interface CameraDeviceWrapper {
@NonNull
CaptureRequest.Builder createCaptureRequest(int var1) throws CameraAccessException;

@TargetApi(VERSION_CODES.P)
void createCaptureSession(SessionConfiguration config) throws CameraAccessException;

@TargetApi(VERSION_CODES.LOLLIPOP)
void createCaptureSession(
@NonNull List<Surface> outputs,
@NonNull CameraCaptureSession.StateCallback callback,
@Nullable Handler handler)
throws CameraAccessException;

void close();
}

class Camera
implements CameraCaptureCallback.CameraCaptureStateListener,
ImageReader.OnImageAvailableListener {
Expand Down Expand Up @@ -114,7 +132,7 @@ class Camera
/** An additional thread for running tasks that shouldn't block the UI. */
private HandlerThread backgroundHandlerThread;

private CameraDevice cameraDevice;
private CameraDeviceWrapper cameraDevice;
private CameraCaptureSession captureSession;
private ImageReader pictureImageReader;
private ImageReader imageStreamReader;
Expand All @@ -136,6 +154,44 @@ class Camera

private MethodChannel.Result flutterResult;

/** A CameraDeviceWrapper implementation that forwards calls to a CameraDevice. */
private class DefaultCameraDeviceWrapper implements CameraDeviceWrapper {
private final CameraDevice cameraDevice;

private DefaultCameraDeviceWrapper(CameraDevice cameraDevice) {
this.cameraDevice = cameraDevice;
}

@NonNull
@Override
public CaptureRequest.Builder createCaptureRequest(int templateType)
throws CameraAccessException {
return cameraDevice.createCaptureRequest(templateType);
}

@TargetApi(VERSION_CODES.P)
@Override
public void createCaptureSession(SessionConfiguration config) throws CameraAccessException {
cameraDevice.createCaptureSession(config);
}

@TargetApi(VERSION_CODES.LOLLIPOP)
@SuppressWarnings("deprecation")
@Override
public void createCaptureSession(
@NonNull List<Surface> outputs,
@NonNull CameraCaptureSession.StateCallback callback,
@Nullable Handler handler)
throws CameraAccessException {
cameraDevice.createCaptureSession(outputs, callback, backgroundHandler);
}

@Override
public void close() {
cameraDevice.close();
}
}

public Camera(
final Activity activity,
final SurfaceTextureEntry flutterTexture,
Expand Down Expand Up @@ -261,7 +317,7 @@ public void open(String imageFormatGroup) throws CameraAccessException {
new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice device) {
cameraDevice = device;
cameraDevice = new DefaultCameraDeviceWrapper(device);
try {
startPreview();
dartMessenger.sendCameraInitializedEvent(
Expand Down Expand Up @@ -584,7 +640,6 @@ public void onCaptureCompleted(

try {
captureSession.stopRepeating();
captureSession.abortCaptures();
Log.i(TAG, "sending capture request");
captureSession.capture(stillBuilder.build(), captureCallback, backgroundHandler);
} catch (CameraAccessException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.SessionConfiguration;
import android.media.ImageReader;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
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;
Expand All @@ -50,11 +54,39 @@
import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
import io.flutter.plugins.camera.utils.TestUtils;
import io.flutter.view.TextureRegistry;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;

class FakeCameraDeviceWrapper implements CameraDeviceWrapper {
final List<CaptureRequest.Builder> captureRequests;

FakeCameraDeviceWrapper(List<CaptureRequest.Builder> captureRequests) {
this.captureRequests = captureRequests;
}

@NonNull
@Override
public CaptureRequest.Builder createCaptureRequest(int var1) {
return captureRequests.remove(0);
}

@Override
public void createCaptureSession(SessionConfiguration config) {}

@Override
public void createCaptureSession(
@NonNull List<Surface> outputs,
@NonNull CameraCaptureSession.StateCallback callback,
@Nullable Handler handler) {}

@Override
public void close() {}
}

public class CameraTest {
private CameraProperties mockCameraProperties;
private CameraFeatureFactory mockCameraFeatureFactory;
Expand Down Expand Up @@ -801,6 +833,29 @@ public void startBackgroundThread_shouldNotStartNewThreadWhenAlreadyCreated() {
verify(mockHandlerThread, times(1)).start();
}

@Test
public void onConverge_shouldTakePictureWithoutAbortingSession() throws CameraAccessException {
ArrayList<CaptureRequest.Builder> mockRequestBuilders = new ArrayList<>();
mockRequestBuilders.add(mock(CaptureRequest.Builder.class));
CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders);
// Stub out other features used by the flow.
TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera);
TestUtils.setPrivateField(camera, "pictureImageReader", mock(ImageReader.class));
SensorOrientationFeature mockSensorOrientationFeature =
mockCameraFeatureFactory.createSensorOrientationFeature(mockCameraProperties, null, null);
DeviceOrientationManager mockDeviceOrientationManager = mock(DeviceOrientationManager.class);
when(mockSensorOrientationFeature.getDeviceOrientationManager())
.thenReturn(mockDeviceOrientationManager);

// Simulate a post-precapture flow.
camera.onConverged();
// A picture should be taken.
verify(mockCaptureSession, times(1)).capture(any(), any(), any());
// The session shuold not be aborted as part of this flow, as this breaks capture on some
// devices, and causes delays on others.
verify(mockCaptureSession, never()).abortCaptures();
}

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/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.9.4+11
version: 0.9.4+12

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down