Skip to content

Commit

Permalink
[camerax] Wrap Android classes/methods required to set the exposure m…
Browse files Browse the repository at this point in the history
…ode (#5966)

Wraps classes/methods needed to set capture request options that will allow us to implement setting the exposure mode, namely:

**Camera2CameraControl**

- `create` method using [`from`](https://developer.android.com/reference/androidx/camera/camera2/interop/Camera2CameraControl#from(androidx.camera.core.CameraControl))
- [`setCaptureRequestOptions`](https://developer.android.com/reference/androidx/camera/camera2/interop/Camera2CameraControl#setCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions))

**CaptureRequestOptions**

- `create` method using its [builder](https://developer.android.com/reference/androidx/camera/camera2/interop/CaptureRequestOptions.Builder)*

Part of flutter/flutter#120468.

*Note that this required that I specify types of supported capture request options due to the Dart/native split. I took inspiration from our previous implementation of supported `LiveData` types (see [pigeon file](https://github.com/flutter/packages/blob/73c9bdc59b8d51ee558bff113bcdcdb53485cd08/packages/camera/camera_android_camerax/pigeons/camerax_library.dart#L80) for details).
  • Loading branch information
camsim99 authored Feb 5, 2024
1 parent d63629d commit 1a5a7ce
Show file tree
Hide file tree
Showing 27 changed files with 1,668 additions and 150 deletions.
4 changes: 4 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.0+31

* Wraps CameraX classes needed to set capture request options, which is needed to implement setting the exposure mode.

## 0.5.0+30

* Adds documentation to clarify how the plugin uses resolution presets as target resolutions for CameraX.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class AnalyzerHostApiImpl implements AnalyzerHostApi {
private final InstanceManager instanceManager;
private final AnalyzerProxy proxy;

/** Proxy for constructors and static method of {@link ImageAnalysis.Analyzer}. */
/** Proxy for constructor of {@link ImageAnalysis.Analyzer}. */
@VisibleForTesting
public static class AnalyzerProxy {

Expand Down Expand Up @@ -95,7 +95,7 @@ public AnalyzerHostApiImpl(
*
* @param binaryMessenger used to communicate with Dart over asynchronous messages
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param proxy proxy for constructors and static method of {@link ImageAnalysis.Analyzer}
* @param proxy proxy for constructor of {@link ImageAnalysis.Analyzer}
*/
@VisibleForTesting
AnalyzerHostApiImpl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class AspectRatioStrategyHostApiImpl implements AspectRatioStrategyHostAp
private final InstanceManager instanceManager;
private final AspectRatioStrategyProxy proxy;

/** Proxy for constructors and static method of {@link AspectRatioStrategy}. */
/** Proxy for constructor of {@link AspectRatioStrategy}. */
@VisibleForTesting
public static class AspectRatioStrategyProxy {
/** Creates an instance of {@link AspectRatioStrategy}. */
Expand All @@ -43,7 +43,7 @@ public AspectRatioStrategyHostApiImpl(@NonNull InstanceManager instanceManager)
* Constructs an {@link AspectRatioStrategyHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param proxy proxy for constructors and static method of {@link AspectRatioStrategy}
* @param proxy proxy for constructor of {@link AspectRatioStrategy}
*/
@VisibleForTesting
AspectRatioStrategyHostApiImpl(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camerax;

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.annotation.VisibleForTesting;
import androidx.camera.camera2.interop.Camera2CameraControl;
import androidx.camera.camera2.interop.CaptureRequestOptions;
import androidx.camera.core.CameraControl;
import androidx.core.content.ContextCompat;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Camera2CameraControlHostApi;
import java.util.Objects;

/**
* Host API implementation for {@link Camera2CameraControl}.
*
* <p>This class may handle instantiating and adding native object instances that are attached to a
* Dart instance or handle method calls on the associated native class or an instance of the class.
*/
public class Camera2CameraControlHostApiImpl implements Camera2CameraControlHostApi {
private final InstanceManager instanceManager;
private final Camera2CameraControlProxy proxy;

/** Proxy for constructor and methods of {@link Camera2CameraControl}. */
@VisibleForTesting
public static class Camera2CameraControlProxy {
Context context;

/**
* Creates an instance of {@link Camera2CameraControl} derived from specified {@link
* CameraControl} instance.
*/
@OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class)
public @NonNull Camera2CameraControl create(@NonNull CameraControl cameraControl) {
return Camera2CameraControl.from(cameraControl);
}

/**
* Adds a {@link CaptureRequestOptions} to update the capture session with the options it
* contains.
*/
@OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class)
public void addCaptureRequestOptions(
@NonNull Camera2CameraControl camera2CameraControl,
@NonNull CaptureRequestOptions bundle,
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
if (context == null) {
throw new IllegalStateException("Context must be set to add capture request options.");
}

ListenableFuture<Void> addCaptureRequestOptionsFuture =
camera2CameraControl.addCaptureRequestOptions(bundle);

Futures.addCallback(
addCaptureRequestOptionsFuture,
new FutureCallback<Void>() {
public void onSuccess(Void voidResult) {
result.success(null);
}

public void onFailure(Throwable t) {
result.error(t);
}
},
ContextCompat.getMainExecutor(context));
}
}

/**
* Constructs a {@link Camera2CameraControlHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param context {@link Context} used to retrieve {@code Executor}
*/
public Camera2CameraControlHostApiImpl(
@NonNull InstanceManager instanceManager, @NonNull Context context) {
this(instanceManager, new Camera2CameraControlProxy(), context);
}

/**
* Constructs a {@link Camera2CameraControlHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param proxy proxy for constructor and methods of {@link Camera2CameraControl}
* @param context {@link Context} used to retrieve {@code Executor}
*/
@VisibleForTesting
Camera2CameraControlHostApiImpl(
@NonNull InstanceManager instanceManager,
@NonNull Camera2CameraControlProxy proxy,
@NonNull Context context) {
this.instanceManager = instanceManager;
this.proxy = proxy;
proxy.context = context;
}

/**
* Sets the context that the {@code Camera2CameraControl} will use to listen for the result of
* setting capture request options.
*
* <p>If using the camera plugin in an add-to-app context, ensure that this is called anytime that
* the context changes.
*/
public void setContext(@NonNull Context context) {
this.proxy.context = context;
}

@Override
public void create(@NonNull Long identifier, @NonNull Long cameraControlIdentifier) {
instanceManager.addDartCreatedInstance(
proxy.create(Objects.requireNonNull(instanceManager.getInstance(cameraControlIdentifier))),
identifier);
}

@Override
public void addCaptureRequestOptions(
@NonNull Long identifier,
@NonNull Long captureRequestOptionsIdentifier,
@NonNull GeneratedCameraXLibrary.Result<Void> result) {
proxy.addCaptureRequestOptions(
getCamera2CameraControlInstance(identifier),
Objects.requireNonNull(instanceManager.getInstance(captureRequestOptionsIdentifier)),
result);
}

/**
* Retrieves the {@link Camera2CameraControl} instance associated with the specified {@code
* identifier}.
*/
private Camera2CameraControl getCamera2CameraControlInstance(@NonNull Long identifier) {
return Objects.requireNonNull(instanceManager.getInstance(identifier));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class CameraControlHostApiImpl implements CameraControlHostApi {
private final InstanceManager instanceManager;
private final CameraControlProxy proxy;

/** Proxy for constructors and static method of {@link CameraControl}. */
/** Proxy for methods of {@link CameraControl}. */
@VisibleForTesting
public static class CameraControlProxy {
Context context;
Expand Down Expand Up @@ -99,6 +99,10 @@ public void startFocusAndMetering(
@NonNull CameraControl cameraControl,
@NonNull FocusMeteringAction focusMeteringAction,
@NonNull GeneratedCameraXLibrary.Result<Long> result) {
if (context == null) {
throw new IllegalStateException("Context must be set to set zoom ratio.");
}

ListenableFuture<FocusMeteringResult> focusMeteringResultFuture =
cameraControl.startFocusAndMetering(focusMeteringAction);

Expand Down Expand Up @@ -173,6 +177,7 @@ public void onFailure(Throwable t) {
* Constructs an {@link CameraControlHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param context {@link Context} used to retrieve {@code Executor}
*/
public CameraControlHostApiImpl(
@NonNull BinaryMessenger binaryMessenger,
Expand All @@ -185,8 +190,8 @@ public CameraControlHostApiImpl(
* Constructs an {@link CameraControlHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param proxy proxy for constructors and static method of {@link CameraControl}
* @param context {@link Context} used to retrieve {@code Executor} used to enable torch mode
* @param proxy proxy for methods of {@link CameraControl}
* @param context {@link Context} used to retrieve {@code Executor}
*/
@VisibleForTesting
CameraControlHostApiImpl(
Expand All @@ -203,11 +208,11 @@ public CameraControlHostApiImpl(
}

/**
* Sets the context that the {@code ProcessCameraProvider} will use to enable/disable torch mode
* and set the zoom ratio.
* Sets the context that the {@code CameraControl} will use to enable/disable torch mode and set
* the zoom ratio.
*
* <p>If using the camera plugin in an add-to-app context, ensure that a new instance of the
* {@code CameraControl} is fetched via {@code #enableTorch} anytime the context changes.
* <p>If using the camera plugin in an add-to-app context, ensure that this is called anytime that
* the context changes.
*/
public void setContext(@NonNull Context context) {
this.proxy.context = context;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camerax;

import android.hardware.camera2.CaptureRequest;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.annotation.VisibleForTesting;
import androidx.camera.camera2.interop.CaptureRequestOptions;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CaptureRequestKeySupportedType;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CaptureRequestOptionsHostApi;
import java.util.HashMap;
import java.util.Map;

/**
* Host API implementation for {@link CaptureRequestOptions}.
*
* <p>This class may handle instantiating and adding native object instances that are attached to a
* Dart instance or handle method calls on the associated native class or an instance of the class.
*/
public class CaptureRequestOptionsHostApiImpl implements CaptureRequestOptionsHostApi {
private final InstanceManager instanceManager;
private final CaptureRequestOptionsProxy proxy;

/** Proxy for constructor of {@link CaptureRequestOptions}. */
@VisibleForTesting
public static class CaptureRequestOptionsProxy {
/** Creates an instance of {@link CaptureRequestOptions}. */
// Suppression is safe because the type shared between the key and value pairs that
// represent capture request options is checked on the Dart side.
@SuppressWarnings("unchecked")
@OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class)
public @NonNull CaptureRequestOptions create(
@NonNull Map<CaptureRequestKeySupportedType, Object> options) {
CaptureRequestOptions.Builder builder = getCaptureRequestOptionsBuilder();

for (Map.Entry<CaptureRequestKeySupportedType, Object> option : options.entrySet()) {
CaptureRequestKeySupportedType optionKeyType = option.getKey();
CaptureRequest.Key<? extends Object> optionKey = getCaptureRequestKey(optionKeyType);
Object optionValue = option.getValue();

if (optionValue == null) {
builder.clearCaptureRequestOption(optionKey);
continue;
}

switch (optionKeyType) {
case CONTROL_AE_LOCK:
builder.setCaptureRequestOption(
(CaptureRequest.Key<Boolean>) optionKey, (Boolean) optionValue);
break;
default:
throw new IllegalArgumentException(
"The capture request key "
+ optionKeyType.toString()
+ "is not currently supported by the plugin.");
}
}

return builder.build();
}

private CaptureRequest.Key<? extends Object> getCaptureRequestKey(
CaptureRequestKeySupportedType type) {
CaptureRequest.Key<? extends Object> key;
switch (type) {
case CONTROL_AE_LOCK:
key = CaptureRequest.CONTROL_AE_LOCK;
break;
default:
throw new IllegalArgumentException(
"The capture request key is not currently supported by the plugin.");
}
return key;
}

@VisibleForTesting
@OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class)
public @NonNull CaptureRequestOptions.Builder getCaptureRequestOptionsBuilder() {
return new CaptureRequestOptions.Builder();
}
}

/**
* Constructs a {@link CaptureRequestOptionsHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
*/
public CaptureRequestOptionsHostApiImpl(@NonNull InstanceManager instanceManager) {
this(instanceManager, new CaptureRequestOptionsProxy());
}

/**
* Constructs a {@link CaptureRequestOptionsHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param proxy proxy for constructor of {@link CaptureRequestOptions}
*/
@VisibleForTesting
CaptureRequestOptionsHostApiImpl(
@NonNull InstanceManager instanceManager, @NonNull CaptureRequestOptionsProxy proxy) {
this.instanceManager = instanceManager;
this.proxy = proxy;
}

@Override
public void create(@NonNull Long identifier, @NonNull Map<Long, Object> options) {
Map<CaptureRequestKeySupportedType, Object> decodedOptions =
new HashMap<CaptureRequestKeySupportedType, Object>();
for (Map.Entry<Long, Object> option : options.entrySet()) {
decodedOptions.put(
CaptureRequestKeySupportedType.values()[option.getKey().intValue()], option.getValue());
}
instanceManager.addDartCreatedInstance(proxy.create(decodedOptions), identifier);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class FallbackStrategyHostApiImpl implements FallbackStrategyHostApi {

private final FallbackStrategyProxy proxy;

/** Proxy for constructors and static method of {@link FallbackStrategy}. */
/** Proxy for constructor of {@link FallbackStrategy}. */
@VisibleForTesting
public static class FallbackStrategyProxy {
/** Creates an instance of {@link FallbackStrategy}. */
Expand Down Expand Up @@ -59,7 +59,7 @@ public FallbackStrategyHostApiImpl(@NonNull InstanceManager instanceManager) {
* Constructs a {@link FallbackStrategyHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param proxy proxy for constructors and static method of {@link FallbackStrategy}
* @param proxy proxy for constructor of {@link FallbackStrategy}
*/
FallbackStrategyHostApiImpl(
@NonNull InstanceManager instanceManager, @NonNull FallbackStrategyProxy proxy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class FocusMeteringActionHostApiImpl implements FocusMeteringActionHostAp

private final FocusMeteringActionProxy proxy;

/** Proxy for constructors and static method of {@link FocusMeteringAction}. */
/** Proxy for constructor of {@link FocusMeteringAction}. */
@VisibleForTesting
public static class FocusMeteringActionProxy {
/** Creates an instance of {@link FocusMeteringAction}. */
Expand Down Expand Up @@ -90,7 +90,7 @@ public FocusMeteringActionHostApiImpl(@NonNull InstanceManager instanceManager)
* Constructs a {@link FocusMeteringActionHostApiImpl}.
*
* @param instanceManager maintains instances stored to communicate with attached Dart objects
* @param proxy proxy for constructors and static method of {@link FocusMeteringAction}
* @param proxy proxy for constructor of {@link FocusMeteringAction}
*/
FocusMeteringActionHostApiImpl(
@NonNull InstanceManager instanceManager, @NonNull FocusMeteringActionProxy proxy) {
Expand Down
Loading

0 comments on commit 1a5a7ce

Please sign in to comment.