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 9 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
5 changes: 5 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.9.4+15

* Fixes a bug resulting in a `CameraAccessException` that prevents image
capture on some Android devices.

## 0.9.4+14

* Restores compatibility with Flutter 2.5 and 2.8.
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 templateType) 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+14
version: 0.9.4+15

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