[camerax] Implement lockCaptureOrientation & unlockCaptureOrientation (#5285)

Implements `lockCaptureOrientation` & `unlockCaptureOrientation` for all camera `UseCase`s. 

Also fixes small bug concerning not initially setting the target rotation of the `UseCase`s to the requested sensor orientation when `createCamera` is called.

Fixes https://github.com/flutter/flutter/issues/125915.
This commit is contained in:
Camille Simon
2024-01-02 11:39:00 -08:00
committed by GitHub
parent 34622dbef9
commit bbb4134751
50 changed files with 2182 additions and 921 deletions

View File

@ -1,3 +1,7 @@
## 0.5.0+25
* Implements `lockCaptureOrientation` and `unlockCaptureOrientation`.
## 0.5.0+24
* Updates example app to use non-deprecated video_player method.

View File

@ -31,10 +31,6 @@ dependencies:
and thus, the plugin will fall back to 480p if configured with a
`ResolutionPreset`.
### Locking/Unlocking capture orientation \[[Issue #125915][125915]\]
`lockCaptureOrientation` & `unLockCaptureOrientation` are unimplemented.
### Exposure mode, point, & offset configuration \[[Issue #120468][120468]\]
`setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented.

View File

@ -27,6 +27,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
private ImageCaptureHostApiImpl imageCaptureHostApiImpl;
private CameraControlHostApiImpl cameraControlHostApiImpl;
public @Nullable SystemServicesHostApiImpl systemServicesHostApiImpl;
public @Nullable DeviceOrientationManagerHostApiImpl deviceOrientationManagerHostApiImpl;
@VisibleForTesting
public @Nullable ProcessCameraProviderHostApiImpl processCameraProviderHostApiImpl;
@ -71,6 +72,10 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
systemServicesHostApiImpl =
new SystemServicesHostApiImpl(binaryMessenger, instanceManager, context);
GeneratedCameraXLibrary.SystemServicesHostApi.setup(binaryMessenger, systemServicesHostApiImpl);
deviceOrientationManagerHostApiImpl =
new DeviceOrientationManagerHostApiImpl(binaryMessenger, instanceManager);
GeneratedCameraXLibrary.DeviceOrientationManagerHostApi.setup(
binaryMessenger, deviceOrientationManagerHostApiImpl);
GeneratedCameraXLibrary.PreviewHostApi.setup(
binaryMessenger, new PreviewHostApiImpl(binaryMessenger, instanceManager, textureRegistry));
imageCaptureHostApiImpl =
@ -145,6 +150,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
systemServicesHostApiImpl.setActivity(activity);
systemServicesHostApiImpl.setPermissionsRegistry(
activityPluginBinding::addRequestPermissionsResultListener);
deviceOrientationManagerHostApiImpl.setActivity(activity);
}
@Override

View File

@ -14,7 +14,6 @@ import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
@ -85,120 +84,6 @@ public class DeviceOrientationManager {
broadcastReceiver = null;
}
/**
* Returns the device's photo orientation in degrees based on the sensor orientation and the last
* known UI orientation.
*
* <p>Returns one of 0, 90, 180 or 270.
*
* @return The device's photo orientation in degrees.
*/
public int getPhotoOrientation() {
return this.getPhotoOrientation(this.lastOrientation);
}
/**
* Returns the device's photo orientation in degrees based on the sensor orientation and the
* supplied {@link PlatformChannel.DeviceOrientation} value.
*
* <p>Returns one of 0, 90, 180 or 270.
*
* @param orientation The {@link PlatformChannel.DeviceOrientation} value that is to be converted
* into degrees.
* @return The device's photo orientation in degrees.
*/
public int getPhotoOrientation(@Nullable PlatformChannel.DeviceOrientation orientation) {
int angle = 0;
// Fallback to device orientation when the orientation value is null.
if (orientation == null) {
orientation = getUIOrientation();
}
switch (orientation) {
case PORTRAIT_UP:
angle = 90;
break;
case PORTRAIT_DOWN:
angle = 270;
break;
case LANDSCAPE_LEFT:
angle = isFrontFacing ? 180 : 0;
break;
case LANDSCAPE_RIGHT:
angle = isFrontFacing ? 0 : 180;
break;
}
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X).
// This has to be taken into account so the JPEG is rotated properly.
// For devices with orientation of 90, this simply returns the mapping from ORIENTATIONS.
// For devices with orientation of 270, the JPEG is rotated 180 degrees instead.
return (angle + sensorOrientation + 270) % 360;
}
/**
* Returns the device's video orientation in clockwise degrees based on the sensor orientation and
* the last known UI orientation.
*
* <p>Returns one of 0, 90, 180 or 270.
*
* @return The device's video orientation in clockwise degrees.
*/
public int getVideoOrientation() {
return this.getVideoOrientation(this.lastOrientation);
}
/**
* Returns the device's video orientation in clockwise degrees based on the sensor orientation and
* the supplied {@link PlatformChannel.DeviceOrientation} value.
*
* <p>Returns one of 0, 90, 180 or 270.
*
* <p>More details can be found in the official Android documentation:
* https://developer.android.com/reference/android/media/MediaRecorder#setOrientationHint(int)
*
* <p>See also:
* https://developer.android.com/training/camera2/camera-preview-large-screens#orientation_calculation
*
* @param orientation The {@link PlatformChannel.DeviceOrientation} value that is to be converted
* into degrees.
* @return The device's video orientation in clockwise degrees.
*/
public int getVideoOrientation(@Nullable PlatformChannel.DeviceOrientation orientation) {
int angle = 0;
// Fallback to device orientation when the orientation value is null.
if (orientation == null) {
orientation = getUIOrientation();
}
switch (orientation) {
case PORTRAIT_UP:
angle = 0;
break;
case PORTRAIT_DOWN:
angle = 180;
break;
case LANDSCAPE_LEFT:
angle = 270;
break;
case LANDSCAPE_RIGHT:
angle = 90;
break;
}
if (isFrontFacing) {
angle *= -1;
}
return (angle + sensorOrientation + 360) % 360;
}
/** @return the last received UI orientation. */
public @Nullable PlatformChannel.DeviceOrientation getLastUIOrientation() {
return this.lastOrientation;
}
/**
* Handles orientation changes based on change events triggered by the OrientationIntentFilter.
*
@ -241,7 +126,7 @@ public class DeviceOrientationManager {
@SuppressWarnings("deprecation")
@VisibleForTesting
PlatformChannel.DeviceOrientation getUIOrientation() {
final int rotation = getDisplay().getRotation();
final int rotation = getDefaultRotation();
final int orientation = activity.getResources().getConfiguration().orientation;
switch (orientation) {
@ -265,57 +150,18 @@ public class DeviceOrientationManager {
}
/**
* Calculates the sensor orientation based on the supplied angle.
* Gets default capture rotation for CameraX {@code UseCase}s.
*
* <p>This method is visible for testing purposes only and should never be used outside this
* class.
* <p>See
* https://developer.android.com/reference/androidx/camera/core/ImageCapture#setTargetRotation(int),
* for instance.
*
* @param angle Orientation angle.
* @return The sensor orientation based on the supplied angle.
* @return The rotation of the screen from its "natural" orientation; one of {@code
* Surface.ROTATION_0}, {@code Surface.ROTATION_90}, {@code Surface.ROTATION_180}, {@code
* Surface.ROTATION_270}
*/
@VisibleForTesting
PlatformChannel.DeviceOrientation calculateSensorOrientation(int angle) {
final int tolerance = 45;
angle += tolerance;
// Orientation is 0 in the default orientation mode. This is portrait-mode for phones
// and landscape for tablets. We have to compensate for this by calculating the default
// orientation, and apply an offset accordingly.
int defaultDeviceOrientation = getDeviceDefaultOrientation();
if (defaultDeviceOrientation == Configuration.ORIENTATION_LANDSCAPE) {
angle += 90;
}
// Determine the orientation
angle = angle % 360;
return new PlatformChannel.DeviceOrientation[] {
PlatformChannel.DeviceOrientation.PORTRAIT_UP,
PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT,
PlatformChannel.DeviceOrientation.PORTRAIT_DOWN,
PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT,
}
[angle / 90];
}
/**
* Gets the default orientation of the device.
*
* <p>This method is visible for testing purposes only and should never be used outside this
* class.
*
* @return The default orientation of the device.
*/
@VisibleForTesting
int getDeviceDefaultOrientation() {
Configuration config = activity.getResources().getConfiguration();
int rotation = getDisplay().getRotation();
if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
&& config.orientation == Configuration.ORIENTATION_LANDSCAPE)
|| ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270)
&& config.orientation == Configuration.ORIENTATION_PORTRAIT)) {
return Configuration.ORIENTATION_LANDSCAPE;
} else {
return Configuration.ORIENTATION_PORTRAIT;
}
int getDefaultRotation() {
return getDisplay().getRotation();
}
/**

View File

@ -0,0 +1,20 @@
// 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 androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.DeviceOrientationManagerFlutterApi;
public class DeviceOrientationManagerFlutterApiImpl extends DeviceOrientationManagerFlutterApi {
public DeviceOrientationManagerFlutterApiImpl(@NonNull BinaryMessenger binaryMessenger) {
super(binaryMessenger);
}
public void sendDeviceOrientationChangedEvent(
@NonNull String orientation, @NonNull Reply<Void> reply) {
super.onDeviceOrientationChanged(orientation, reply);
}
}

View File

@ -0,0 +1,104 @@
// 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.app.Activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.CameraPermissionsManager.PermissionsRegistry;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.DeviceOrientationManagerHostApi;
public class DeviceOrientationManagerHostApiImpl implements DeviceOrientationManagerHostApi {
private final BinaryMessenger binaryMessenger;
private final InstanceManager instanceManager;
@VisibleForTesting public @NonNull CameraXProxy cameraXProxy = new CameraXProxy();
@VisibleForTesting public @Nullable DeviceOrientationManager deviceOrientationManager;
@VisibleForTesting
public @NonNull DeviceOrientationManagerFlutterApiImpl deviceOrientationManagerFlutterApiImpl;
private Activity activity;
private PermissionsRegistry permissionsRegistry;
public DeviceOrientationManagerHostApiImpl(
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
this.binaryMessenger = binaryMessenger;
this.instanceManager = instanceManager;
this.deviceOrientationManagerFlutterApiImpl =
new DeviceOrientationManagerFlutterApiImpl(binaryMessenger);
}
public void setActivity(@NonNull Activity activity) {
this.activity = activity;
}
/**
* Starts listening for device orientation changes using an instance of a {@link
* DeviceOrientationManager}.
*
* <p>Whenever a change in device orientation is detected by the {@code DeviceOrientationManager},
* the {@link SystemServicesFlutterApi} will be used to notify the Dart side.
*/
@Override
public void startListeningForDeviceOrientationChange(
@NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation) {
deviceOrientationManager =
cameraXProxy.createDeviceOrientationManager(
activity,
isFrontFacing,
sensorOrientation.intValue(),
(DeviceOrientation newOrientation) -> {
deviceOrientationManagerFlutterApiImpl.sendDeviceOrientationChangedEvent(
serializeDeviceOrientation(newOrientation), reply -> {});
});
deviceOrientationManager.start();
}
/** Serializes {@code DeviceOrientation} into a String that the Dart side is able to recognize. */
String serializeDeviceOrientation(DeviceOrientation orientation) {
return orientation.toString();
}
/**
* Tells the {@code deviceOrientationManager} to stop listening for orientation updates.
*
* <p>Has no effect if the {@code deviceOrientationManager} was never created to listen for device
* orientation updates.
*/
@Override
public void stopListeningForDeviceOrientationChange() {
if (deviceOrientationManager != null) {
deviceOrientationManager.stop();
}
}
/**
* Gets default capture rotation for CameraX {@code UseCase}s.
*
* <p>The default capture rotation for CameraX is the rotation of default {@code Display} at the
* time that a {@code UseCase} is bound, but the default {@code Display} does not change in this
* plugin, so this value is {@code Display}-agnostic.
*
* <p>See
* https://developer.android.com/reference/androidx/camera/core/ImageCapture#setTargetRotation(int)
* for instance for more information on how this default value is used.
*/
@Override
public @NonNull Long getDefaultDisplayRotation() {
int defaultRotation;
try {
defaultRotation = deviceOrientationManager.getDefaultRotation();
} catch (NullPointerException e) {
throw new IllegalStateException(
"startListeningForDeviceOrientationChange must first be called to subscribe to device orientation changes in order to retrieve default rotation.");
}
return Long.valueOf(defaultRotation);
}
}

View File

@ -1256,11 +1256,6 @@ public class GeneratedCameraXLibrary {
void requestCameraPermissions(
@NonNull Boolean enableAudio, @NonNull Result<CameraPermissionsErrorData> result);
void startListeningForDeviceOrientationChange(
@NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation);
void stopListeningForDeviceOrientationChange();
@NonNull
String getTempFilePath(@NonNull String prefix, @NonNull String suffix);
@ -1305,57 +1300,6 @@ public class GeneratedCameraXLibrary {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Boolean isFrontFacingArg = (Boolean) args.get(0);
Number sensorOrientationArg = (Number) args.get(1);
try {
api.startListeningForDeviceOrientationChange(
isFrontFacingArg,
(sensorOrientationArg == null) ? null : sensorOrientationArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
api.stopListeningForDeviceOrientationChange();
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
@ -1402,18 +1346,6 @@ public class GeneratedCameraXLibrary {
return new StandardMessageCodec();
}
public void onDeviceOrientationChanged(
@NonNull String orientationArg, @NonNull Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged",
getCodec());
channel.send(
new ArrayList<Object>(Collections.singletonList(orientationArg)),
channelReply -> callback.reply(null));
}
public void onCameraError(@NonNull String errorDescriptionArg, @NonNull Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
@ -1425,6 +1357,133 @@ public class GeneratedCameraXLibrary {
channelReply -> callback.reply(null));
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface DeviceOrientationManagerHostApi {
void startListeningForDeviceOrientationChange(
@NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation);
void stopListeningForDeviceOrientationChange();
@NonNull
Long getDefaultDisplayRotation();
/** The codec used by DeviceOrientationManagerHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
}
/**
* Sets up an instance of `DeviceOrientationManagerHostApi` to handle messages through the
* `binaryMessenger`.
*/
static void setup(
@NonNull BinaryMessenger binaryMessenger, @Nullable DeviceOrientationManagerHostApi api) {
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.DeviceOrientationManagerHostApi.startListeningForDeviceOrientationChange",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Boolean isFrontFacingArg = (Boolean) args.get(0);
Number sensorOrientationArg = (Number) args.get(1);
try {
api.startListeningForDeviceOrientationChange(
isFrontFacingArg,
(sensorOrientationArg == null) ? null : sensorOrientationArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.DeviceOrientationManagerHostApi.stopListeningForDeviceOrientationChange",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
api.stopListeningForDeviceOrientationChange();
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.DeviceOrientationManagerHostApi.getDefaultDisplayRotation",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
Long output = api.getDefaultDisplayRotation();
wrapped.add(0, output);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
public static class DeviceOrientationManagerFlutterApi {
private final @NonNull BinaryMessenger binaryMessenger;
public DeviceOrientationManagerFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
this.binaryMessenger = argBinaryMessenger;
}
/** Public interface for sending reply. */
@SuppressWarnings("UnknownNullness")
public interface Reply<T> {
void reply(T reply);
}
/** The codec used by DeviceOrientationManagerFlutterApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
}
public void onDeviceOrientationChanged(
@NonNull String orientationArg, @NonNull Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.DeviceOrientationManagerFlutterApi.onDeviceOrientationChanged",
getCodec());
channel.send(
new ArrayList<Object>(Collections.singletonList(orientationArg)),
channelReply -> callback.reply(null));
}
}
private static class PreviewHostApiCodec extends StandardMessageCodec {
public static final PreviewHostApiCodec INSTANCE = new PreviewHostApiCodec();
@ -1466,6 +1525,8 @@ public class GeneratedCameraXLibrary {
@NonNull
ResolutionInfo getResolutionInfo(@NonNull Long identifier);
void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation);
/** The codec used by PreviewHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return PreviewHostApiCodec.INSTANCE;
@ -1577,6 +1638,32 @@ public class GeneratedCameraXLibrary {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.PreviewHostApi.setTargetRotation", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
Number rotationArg = (Number) args.get(1);
try {
api.setTargetRotation(
(identifierArg == null) ? null : identifierArg.longValue(),
(rotationArg == null) ? null : rotationArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
@ -1588,6 +1675,8 @@ public class GeneratedCameraXLibrary {
@NonNull
Long getOutput(@NonNull Long identifier);
void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation);
/** The codec used by VideoCaptureHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
@ -1646,6 +1735,34 @@ public class GeneratedCameraXLibrary {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.VideoCaptureHostApi.setTargetRotation",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
Number rotationArg = (Number) args.get(1);
try {
api.setTargetRotation(
(identifierArg == null) ? null : identifierArg.longValue(),
(rotationArg == null) ? null : rotationArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
@ -2055,12 +2172,17 @@ public class GeneratedCameraXLibrary {
public interface ImageCaptureHostApi {
void create(
@NonNull Long identifier, @Nullable Long flashMode, @Nullable Long resolutionSelectorId);
@NonNull Long identifier,
@Nullable Long targetRotation,
@Nullable Long flashMode,
@Nullable Long resolutionSelectorId);
void setFlashMode(@NonNull Long identifier, @NonNull Long flashMode);
void takePicture(@NonNull Long identifier, @NonNull Result<String> result);
void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation);
/** The codec used by ImageCaptureHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
@ -2080,11 +2202,13 @@ public class GeneratedCameraXLibrary {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
Number flashModeArg = (Number) args.get(1);
Number resolutionSelectorIdArg = (Number) args.get(2);
Number targetRotationArg = (Number) args.get(1);
Number flashModeArg = (Number) args.get(2);
Number resolutionSelectorIdArg = (Number) args.get(3);
try {
api.create(
(identifierArg == null) ? null : identifierArg.longValue(),
(targetRotationArg == null) ? null : targetRotationArg.longValue(),
(flashModeArg == null) ? null : flashModeArg.longValue(),
(resolutionSelectorIdArg == null)
? null
@ -2156,6 +2280,34 @@ public class GeneratedCameraXLibrary {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.ImageCaptureHostApi.setTargetRotation",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
Number rotationArg = (Number) args.get(1);
try {
api.setTargetRotation(
(identifierArg == null) ? null : identifierArg.longValue(),
(rotationArg == null) ? null : rotationArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
@ -2486,12 +2638,17 @@ public class GeneratedCameraXLibrary {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface ImageAnalysisHostApi {
void create(@NonNull Long identifier, @Nullable Long resolutionSelectorId);
void create(
@NonNull Long identifier,
@Nullable Long targetRotation,
@Nullable Long resolutionSelectorId);
void setAnalyzer(@NonNull Long identifier, @NonNull Long analyzerIdentifier);
void clearAnalyzer(@NonNull Long identifier);
void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation);
/** The codec used by ImageAnalysisHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
@ -2512,10 +2669,12 @@ public class GeneratedCameraXLibrary {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
Number resolutionSelectorIdArg = (Number) args.get(1);
Number targetRotationArg = (Number) args.get(1);
Number resolutionSelectorIdArg = (Number) args.get(2);
try {
api.create(
(identifierArg == null) ? null : identifierArg.longValue(),
(targetRotationArg == null) ? null : targetRotationArg.longValue(),
(resolutionSelectorIdArg == null)
? null
: resolutionSelectorIdArg.longValue());
@ -2581,6 +2740,34 @@ public class GeneratedCameraXLibrary {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.ImageAnalysisHostApi.setTargetRotation",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
Number rotationArg = (Number) args.get(1);
try {
api.setTargetRotation(
(identifierArg == null) ? null : identifierArg.longValue(),
(rotationArg == null) ? null : rotationArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */

View File

@ -41,9 +41,13 @@ public class ImageAnalysisHostApiImpl implements ImageAnalysisHostApi {
/** Creates an {@link ImageAnalysis} instance with the target resolution if specified. */
@Override
public void create(@NonNull Long identifier, @Nullable Long resolutionSelectorId) {
public void create(
@NonNull Long identifier, @Nullable Long rotation, @Nullable Long resolutionSelectorId) {
ImageAnalysis.Builder imageAnalysisBuilder = cameraXProxy.createImageAnalysisBuilder();
if (rotation != null) {
imageAnalysisBuilder.setTargetRotation(rotation.intValue());
}
if (resolutionSelectorId != null) {
ResolutionSelector resolutionSelector =
Objects.requireNonNull(instanceManager.getInstance(resolutionSelectorId));
@ -75,6 +79,13 @@ public class ImageAnalysisHostApiImpl implements ImageAnalysisHostApi {
imageAnalysis.clearAnalyzer();
}
/** Dynamically sets the target rotation of the {@link ImageAnalysis}. */
@Override
public void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation) {
ImageAnalysis imageAnalysis = getImageAnalysisInstance(identifier);
imageAnalysis.setTargetRotation(rotation.intValue());
}
/**
* Retrieives the {@link ImageAnalysis} instance associated with the specified {@code identifier}.
*/

View File

@ -53,9 +53,15 @@ public class ImageCaptureHostApiImpl implements ImageCaptureHostApi {
*/
@Override
public void create(
@NonNull Long identifier, @Nullable Long flashMode, @Nullable Long resolutionSelectorId) {
@NonNull Long identifier,
@Nullable Long rotation,
@Nullable Long flashMode,
@Nullable Long resolutionSelectorId) {
ImageCapture.Builder imageCaptureBuilder = cameraXProxy.createImageCaptureBuilder();
if (rotation != null) {
imageCaptureBuilder.setTargetRotation(rotation.intValue());
}
if (flashMode != null) {
// This sets the requested flash mode, but may fail silently.
imageCaptureBuilder.setFlashMode(flashMode.intValue());
@ -73,8 +79,7 @@ public class ImageCaptureHostApiImpl implements ImageCaptureHostApi {
/** Sets the flash mode of the {@link ImageCapture} instance with the specified identifier. */
@Override
public void setFlashMode(@NonNull Long identifier, @NonNull Long flashMode) {
ImageCapture imageCapture =
(ImageCapture) Objects.requireNonNull(instanceManager.getInstance(identifier));
ImageCapture imageCapture = getImageCaptureInstance(identifier);
imageCapture.setFlashMode(flashMode.intValue());
}
@ -82,8 +87,7 @@ public class ImageCaptureHostApiImpl implements ImageCaptureHostApi {
@Override
public void takePicture(
@NonNull Long identifier, @NonNull GeneratedCameraXLibrary.Result<String> result) {
ImageCapture imageCapture =
(ImageCapture) Objects.requireNonNull(instanceManager.getInstance(identifier));
ImageCapture imageCapture = getImageCaptureInstance(identifier);
final File outputDir = context.getCacheDir();
File temporaryCaptureFile;
try {
@ -118,4 +122,18 @@ public class ImageCaptureHostApiImpl implements ImageCaptureHostApi {
}
};
}
/** Dynamically sets the target rotation of the {@link ImageCapture}. */
@Override
public void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation) {
ImageCapture imageCapture = getImageCaptureInstance(identifier);
imageCapture.setTargetRotation(rotation.intValue());
}
/**
* Retrieves the {@link ImageCapture} instance associated with the specified {@code identifier}.
*/
private ImageCapture getImageCaptureInstance(@NonNull Long identifier) {
return Objects.requireNonNull(instanceManager.getInstance(identifier));
}
}

View File

@ -75,7 +75,14 @@ public class PendingRecordingHostApiImpl implements PendingRecordingHostApi {
if (event instanceof VideoRecordEvent.Finalize) {
VideoRecordEvent.Finalize castedEvent = (VideoRecordEvent.Finalize) event;
if (castedEvent.hasError()) {
systemServicesFlutterApi.sendCameraError(castedEvent.getCause().toString(), reply -> {});
String cameraErrorMessage;
if (castedEvent.getCause() != null) {
cameraErrorMessage = castedEvent.getCause().toString();
} else {
cameraErrorMessage =
"Error code " + castedEvent.getError() + ": An error occurred while recording video.";
}
systemServicesFlutterApi.sendCameraError(cameraErrorMessage, reply -> {});
}
}
}

View File

@ -61,7 +61,7 @@ public class PreviewHostApiImpl implements PreviewHostApi {
*/
@Override
public @NonNull Long setSurfaceProvider(@NonNull Long identifier) {
Preview preview = (Preview) Objects.requireNonNull(instanceManager.getInstance(identifier));
Preview preview = getPreviewInstance(identifier);
flutterSurfaceTexture = textureRegistry.createSurfaceTexture();
SurfaceTexture surfaceTexture = flutterSurfaceTexture.surfaceTexture();
Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(surfaceTexture);
@ -142,7 +142,7 @@ public class PreviewHostApiImpl implements PreviewHostApi {
@Override
public @NonNull GeneratedCameraXLibrary.ResolutionInfo getResolutionInfo(
@NonNull Long identifier) {
Preview preview = (Preview) Objects.requireNonNull(instanceManager.getInstance(identifier));
Preview preview = getPreviewInstance(identifier);
Size resolution = preview.getResolutionInfo().getResolution();
GeneratedCameraXLibrary.ResolutionInfo.Builder resolutionInfo =
@ -151,4 +151,16 @@ public class PreviewHostApiImpl implements PreviewHostApi {
.setHeight(Long.valueOf(resolution.getHeight()));
return resolutionInfo.build();
}
/** Dynamically sets the target rotation of the {@link Preview}. */
@Override
public void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation) {
Preview preview = getPreviewInstance(identifier);
preview.setTargetRotation(rotation.intValue());
}
/** Retrieves the {@link Preview} instance associated with the specified {@code identifier}. */
private Preview getPreviewInstance(@NonNull Long identifier) {
return Objects.requireNonNull(instanceManager.getInstance(identifier));
}
}

View File

@ -13,11 +13,6 @@ public class SystemServicesFlutterApiImpl extends SystemServicesFlutterApi {
super(binaryMessenger);
}
public void sendDeviceOrientationChangedEvent(
@NonNull String orientation, @NonNull Reply<Void> reply) {
super.onDeviceOrientationChanged(orientation, reply);
}
public void sendCameraError(@NonNull String errorDescription, @NonNull Reply<Void> reply) {
super.onCameraError(errorDescription, reply);
}

View File

@ -7,14 +7,11 @@ package io.flutter.plugins.camerax;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.CameraPermissionsManager.PermissionsRegistry;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraPermissionsErrorData;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Result;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesHostApi;
import java.io.File;
import java.io.IOException;
@ -25,7 +22,6 @@ public class SystemServicesHostApiImpl implements SystemServicesHostApi {
private Context context;
@VisibleForTesting public @NonNull CameraXProxy cameraXProxy = new CameraXProxy();
@VisibleForTesting public @Nullable DeviceOrientationManager deviceOrientationManager;
@VisibleForTesting public @NonNull SystemServicesFlutterApiImpl systemServicesFlutterApi;
private Activity activity;
@ -84,46 +80,6 @@ public class SystemServicesHostApiImpl implements SystemServicesHostApi {
});
}
/**
* Starts listening for device orientation changes using an instance of a {@link
* DeviceOrientationManager}.
*
* <p>Whenever a change in device orientation is detected by the {@code DeviceOrientationManager},
* the {@link SystemServicesFlutterApi} will be used to notify the Dart side.
*/
@Override
public void startListeningForDeviceOrientationChange(
@NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation) {
deviceOrientationManager =
cameraXProxy.createDeviceOrientationManager(
activity,
isFrontFacing,
sensorOrientation.intValue(),
(DeviceOrientation newOrientation) -> {
systemServicesFlutterApi.sendDeviceOrientationChangedEvent(
serializeDeviceOrientation(newOrientation), reply -> {});
});
deviceOrientationManager.start();
}
/** Serializes {@code DeviceOrientation} into a String that the Dart side is able to recognize. */
String serializeDeviceOrientation(DeviceOrientation orientation) {
return orientation.toString();
}
/**
* Tells the {@code deviceOrientationManager} to stop listening for orientation updates.
*
* <p>Has no effect if the {@code deviceOrientationManager} was never created to listen for device
* orientation updates.
*/
@Override
public void stopListeningForDeviceOrientationChange() {
if (deviceOrientationManager != null) {
deviceOrientationManager.stop();
}
}
/** Returns a path to be used to create a temp file in the current cache directory. */
@Override
@NonNull

View File

@ -37,8 +37,7 @@ public class VideoCaptureHostApiImpl implements VideoCaptureHostApi {
@Override
@NonNull
public Long getOutput(@NonNull Long identifier) {
VideoCapture<Recorder> videoCapture =
Objects.requireNonNull(instanceManager.getInstance(identifier));
VideoCapture<Recorder> videoCapture = getVideoCaptureInstance(identifier);
Recorder recorder = videoCapture.getOutput();
return Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(recorder));
}
@ -49,4 +48,18 @@ public class VideoCaptureHostApiImpl implements VideoCaptureHostApi {
@NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) {
return new VideoCaptureFlutterApiImpl(binaryMessenger, instanceManager);
}
/** Dynamically sets the target rotation of the {@link VideoCapture}. */
@Override
public void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation) {
VideoCapture<Recorder> videoCapture = getVideoCaptureInstance(identifier);
videoCapture.setTargetRotation(rotation.intValue());
}
/**
* Retrieves the {@link VideoCapture} instance associated with the specified {@code identifier}.
*/
private VideoCapture<Recorder> getVideoCaptureInstance(@NonNull Long identifier) {
return Objects.requireNonNull(instanceManager.getInstance(identifier));
}
}

View File

@ -44,6 +44,7 @@ public class CameraAndroidCameraxPluginTest {
plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl;
plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl;
plugin.systemServicesHostApiImpl = mock(SystemServicesHostApiImpl.class);
plugin.deviceOrientationManagerHostApiImpl = mock(DeviceOrientationManagerHostApiImpl.class);
plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onAttachedToActivity(activityPluginBinding);
@ -68,6 +69,7 @@ public class CameraAndroidCameraxPluginTest {
plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl;
plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl;
plugin.systemServicesHostApiImpl = mock(SystemServicesHostApiImpl.class);
plugin.deviceOrientationManagerHostApiImpl = mock(DeviceOrientationManagerHostApiImpl.class);
plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onAttachedToActivity(activityPluginBinding);

View File

@ -50,108 +50,6 @@ public class DeviceOrientationManagerTest {
new DeviceOrientationManager(mockActivity, false, 0, mockDeviceOrientationChangeCallback);
}
@Test
public void getVideoOrientation_whenNaturalScreenOrientationEqualsPortraitUp() {
int degreesPortraitUp =
deviceOrientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_UP);
int degreesPortraitDown =
deviceOrientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_DOWN);
int degreesLandscapeLeft =
deviceOrientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_LEFT);
int degreesLandscapeRight =
deviceOrientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
assertEquals(0, degreesPortraitUp);
assertEquals(270, degreesLandscapeLeft);
assertEquals(180, degreesPortraitDown);
assertEquals(90, degreesLandscapeRight);
}
@Test
public void getVideoOrientation_whenNaturalScreenOrientationEqualsLandscapeLeft() {
DeviceOrientationManager orientationManager =
new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback);
int degreesPortraitUp = orientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_UP);
int degreesPortraitDown =
orientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_DOWN);
int degreesLandscapeLeft =
orientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_LEFT);
int degreesLandscapeRight =
orientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
assertEquals(90, degreesPortraitUp);
assertEquals(0, degreesLandscapeLeft);
assertEquals(270, degreesPortraitDown);
assertEquals(180, degreesLandscapeRight);
}
@Test
public void getVideoOrientation_fallbackToPortraitSensorOrientationWhenOrientationIsNull() {
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0);
int degrees = deviceOrientationManager.getVideoOrientation(null);
assertEquals(0, degrees);
}
@Test
public void getVideoOrientation_fallbackToLandscapeSensorOrientationWhenOrientationIsNull() {
setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0);
DeviceOrientationManager orientationManager =
new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback);
int degrees = orientationManager.getVideoOrientation(null);
assertEquals(0, degrees);
}
@Test
public void getPhotoOrientation_whenNaturalScreenOrientationEqualsPortraitUp() {
int degreesPortraitUp =
deviceOrientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_UP);
int degreesPortraitDown =
deviceOrientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_DOWN);
int degreesLandscapeLeft =
deviceOrientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_LEFT);
int degreesLandscapeRight =
deviceOrientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
assertEquals(0, degreesPortraitUp);
assertEquals(90, degreesLandscapeRight);
assertEquals(180, degreesPortraitDown);
assertEquals(270, degreesLandscapeLeft);
}
@Test
public void getPhotoOrientation_whenNaturalScreenOrientationEqualsLandscapeLeft() {
DeviceOrientationManager orientationManager =
new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback);
int degreesPortraitUp = orientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_UP);
int degreesPortraitDown =
orientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_DOWN);
int degreesLandscapeLeft =
orientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_LEFT);
int degreesLandscapeRight =
orientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_RIGHT);
assertEquals(90, degreesPortraitUp);
assertEquals(180, degreesLandscapeRight);
assertEquals(270, degreesPortraitDown);
assertEquals(0, degreesLandscapeLeft);
}
@Test
public void getPhotoOrientation_shouldFallbackToCurrentOrientationWhenOrientationIsNull() {
setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0);
int degrees = deviceOrientationManager.getPhotoOrientation(null);
assertEquals(270, degrees);
}
@Test
public void handleUIOrientationChange_shouldSendMessageWhenSensorAccessIsAllowed() {
try (MockedStatic<Settings.System> mockedSystem = mockStatic(Settings.System.class)) {
@ -239,60 +137,6 @@ public class DeviceOrientationManagerTest {
assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation);
}
@Test
public void getDeviceDefaultOrientation() {
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0);
int orientation = deviceOrientationManager.getDeviceDefaultOrientation();
assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_180);
orientation = deviceOrientationManager.getDeviceDefaultOrientation();
assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_90);
orientation = deviceOrientationManager.getDeviceDefaultOrientation();
assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_270);
orientation = deviceOrientationManager.getDeviceDefaultOrientation();
assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0);
orientation = deviceOrientationManager.getDeviceDefaultOrientation();
assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_180);
orientation = deviceOrientationManager.getDeviceDefaultOrientation();
assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_90);
orientation = deviceOrientationManager.getDeviceDefaultOrientation();
assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_270);
orientation = deviceOrientationManager.getDeviceDefaultOrientation();
assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation);
}
@Test
public void calculateSensorOrientation() {
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0);
DeviceOrientation orientation = deviceOrientationManager.calculateSensorOrientation(0);
assertEquals(DeviceOrientation.PORTRAIT_UP, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0);
orientation = deviceOrientationManager.calculateSensorOrientation(90);
assertEquals(DeviceOrientation.LANDSCAPE_LEFT, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0);
orientation = deviceOrientationManager.calculateSensorOrientation(180);
assertEquals(DeviceOrientation.PORTRAIT_DOWN, orientation);
setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0);
orientation = deviceOrientationManager.calculateSensorOrientation(270);
assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, orientation);
}
private void setUpUIOrientationMocks(int orientation, int rotation) {
Resources mockResources = mock(Resources.class);
Configuration mockConfiguration = mock(Configuration.class);
@ -304,6 +148,16 @@ public class DeviceOrientationManagerTest {
when(mockResources.getConfiguration()).thenReturn(mockConfiguration);
}
@Test
public void getDefaultRotation_returnsExpectedValue() {
final int expectedRotation = 90;
when(mockDisplay.getRotation()).thenReturn(expectedRotation);
final int defaultRotation = deviceOrientationManager.getDefaultRotation();
assertEquals(defaultRotation, expectedRotation);
}
@Test
public void getDisplayTest() {
Display display = deviceOrientationManager.getDisplay();

View File

@ -0,0 +1,96 @@
// 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 static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.DeviceOrientationManager.DeviceOrientationChangeCallback;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.DeviceOrientationManagerFlutterApi.Reply;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
public class DeviceOrientationManagerWrapperTest {
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@Mock DeviceOrientationManager mockDeviceOrientationManager;
@Mock public BinaryMessenger mockBinaryMessenger;
@Mock public InstanceManager mockInstanceManager;
@Test
public void deviceOrientationManagerWrapper_handlesDeviceOrientationChangesAsExpected() {
final DeviceOrientationManagerHostApiImpl hostApi =
new DeviceOrientationManagerHostApiImpl(mockBinaryMessenger, mockInstanceManager);
final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class);
final Activity mockActivity = mock(Activity.class);
final Boolean isFrontFacing = true;
final int sensorOrientation = 90;
DeviceOrientationManagerFlutterApiImpl flutterApi =
mock(DeviceOrientationManagerFlutterApiImpl.class);
hostApi.deviceOrientationManagerFlutterApiImpl = flutterApi;
hostApi.cameraXProxy = mockCameraXProxy;
hostApi.setActivity(mockActivity);
when(mockCameraXProxy.createDeviceOrientationManager(
eq(mockActivity),
eq(isFrontFacing),
eq(sensorOrientation),
any(DeviceOrientationChangeCallback.class)))
.thenReturn(mockDeviceOrientationManager);
final ArgumentCaptor<DeviceOrientationChangeCallback> deviceOrientationChangeCallbackCaptor =
ArgumentCaptor.forClass(DeviceOrientationChangeCallback.class);
hostApi.startListeningForDeviceOrientationChange(
isFrontFacing, Long.valueOf(sensorOrientation));
// Test callback method defined in Flutter API is called when device orientation changes.
verify(mockCameraXProxy)
.createDeviceOrientationManager(
eq(mockActivity),
eq(isFrontFacing),
eq(sensorOrientation),
deviceOrientationChangeCallbackCaptor.capture());
DeviceOrientationChangeCallback deviceOrientationChangeCallback =
deviceOrientationChangeCallbackCaptor.getValue();
deviceOrientationChangeCallback.onChange(DeviceOrientation.PORTRAIT_DOWN);
verify(flutterApi)
.sendDeviceOrientationChangedEvent(
eq(DeviceOrientation.PORTRAIT_DOWN.toString()), ArgumentMatchers.<Reply<Void>>any());
// Test that the DeviceOrientationManager starts listening for device orientation changes.
verify(mockDeviceOrientationManager).start();
// Test that the DeviceOrientationManager can stop listening for device orientation changes.
hostApi.stopListeningForDeviceOrientationChange();
verify(mockDeviceOrientationManager).stop();
}
@Test
public void getDefaultDisplayRotation_returnsExpectedRotation() {
final DeviceOrientationManagerHostApiImpl hostApi =
new DeviceOrientationManagerHostApiImpl(mockBinaryMessenger, mockInstanceManager);
final int defaultRotation = 180;
hostApi.deviceOrientationManager = mockDeviceOrientationManager;
when(mockDeviceOrientationManager.getDefaultRotation()).thenReturn(defaultRotation);
assertEquals(hostApi.getDefaultDisplayRotation(), Long.valueOf(defaultRotation));
}
}

View File

@ -12,6 +12,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.view.Surface;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.resolutionselector.ResolutionSelector;
import androidx.test.core.app.ApplicationProvider;
@ -56,6 +57,7 @@ public class ImageAnalysisTest {
final ResolutionSelector mockResolutionSelector = mock(ResolutionSelector.class);
final long instanceIdentifier = 0;
final long mockResolutionSelectorId = 25;
final int targetRotation = Surface.ROTATION_90;
hostApi.cameraXProxy = mockCameraXProxy;
instanceManager.addDartCreatedInstance(mockResolutionSelector, mockResolutionSelectorId);
@ -63,8 +65,9 @@ public class ImageAnalysisTest {
when(mockCameraXProxy.createImageAnalysisBuilder()).thenReturn(mockImageAnalysisBuilder);
when(mockImageAnalysisBuilder.build()).thenReturn(mockImageAnalysis);
hostApi.create(instanceIdentifier, mockResolutionSelectorId);
hostApi.create(instanceIdentifier, Long.valueOf(targetRotation), mockResolutionSelectorId);
verify(mockImageAnalysisBuilder).setTargetRotation(targetRotation);
verify(mockImageAnalysisBuilder).setResolutionSelector(mockResolutionSelector);
assertEquals(instanceManager.getInstance(instanceIdentifier), mockImageAnalysis);
}
@ -98,4 +101,18 @@ public class ImageAnalysisTest {
verify(mockImageAnalysis).clearAnalyzer();
}
@Test
public void setTargetRotation_makesCallToSetTargetRotation() {
final ImageAnalysisHostApiImpl hostApi =
new ImageAnalysisHostApiImpl(mockBinaryMessenger, instanceManager, context);
final long instanceIdentifier = 32;
final int targetRotation = Surface.ROTATION_180;
instanceManager.addDartCreatedInstance(mockImageAnalysis, instanceIdentifier);
hostApi.setTargetRotation(instanceIdentifier, Long.valueOf(targetRotation));
verify(mockImageAnalysis).setTargetRotation(targetRotation);
}
}

View File

@ -14,6 +14,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.view.Surface;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.resolutionselector.ResolutionSelector;
@ -63,18 +64,24 @@ public class ImageCaptureTest {
new ImageCaptureHostApiImpl(mockBinaryMessenger, testInstanceManager, context);
final ImageCapture.Builder mockImageCaptureBuilder = mock(ImageCapture.Builder.class);
final Long imageCaptureIdentifier = 74L;
final Long flashMode = Long.valueOf(ImageCapture.FLASH_MODE_ON);
final int flashMode = ImageCapture.FLASH_MODE_ON;
final ResolutionSelector mockResolutionSelector = mock(ResolutionSelector.class);
final long mockResolutionSelectorId = 77;
final int targetRotation = Surface.ROTATION_270;
imageCaptureHostApiImpl.cameraXProxy = mockCameraXProxy;
testInstanceManager.addDartCreatedInstance(mockResolutionSelector, mockResolutionSelectorId);
when(mockCameraXProxy.createImageCaptureBuilder()).thenReturn(mockImageCaptureBuilder);
when(mockImageCaptureBuilder.build()).thenReturn(mockImageCapture);
imageCaptureHostApiImpl.create(imageCaptureIdentifier, flashMode, mockResolutionSelectorId);
imageCaptureHostApiImpl.create(
imageCaptureIdentifier,
Long.valueOf(targetRotation),
Long.valueOf(flashMode),
mockResolutionSelectorId);
verify(mockImageCaptureBuilder).setFlashMode(flashMode.intValue());
verify(mockImageCaptureBuilder).setTargetRotation(targetRotation);
verify(mockImageCaptureBuilder).setFlashMode(flashMode);
verify(mockImageCaptureBuilder).setResolutionSelector(mockResolutionSelector);
verify(mockImageCaptureBuilder).build();
verify(testInstanceManager).addDartCreatedInstance(mockImageCapture, imageCaptureIdentifier);
@ -197,4 +204,18 @@ public class ImageCaptureTest {
verify(mockResult).error(mockException);
}
@Test
public void setTargetRotation_makesCallToSetTargetRotation() {
final ImageCaptureHostApiImpl hostApi =
new ImageCaptureHostApiImpl(mockBinaryMessenger, testInstanceManager, context);
final long instanceIdentifier = 42;
final int targetRotation = Surface.ROTATION_90;
testInstanceManager.addDartCreatedInstance(mockImageCapture, instanceIdentifier);
hostApi.setTargetRotation(instanceIdentifier, Long.valueOf(targetRotation));
verify(mockImageCapture).setTargetRotation(targetRotation);
}
}

View File

@ -217,4 +217,18 @@ public class PreviewTest {
assertEquals(resolutionInfo.getWidth(), Long.valueOf(resolutionWidth));
assertEquals(resolutionInfo.getHeight(), Long.valueOf(resolutionHeight));
}
@Test
public void setTargetRotation_makesCallToSetTargetRotation() {
final PreviewHostApiImpl hostApi =
new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry);
final long instanceIdentifier = 52;
final int targetRotation = Surface.ROTATION_180;
testInstanceManager.addDartCreatedInstance(mockPreview, instanceIdentifier);
hostApi.setTargetRotation(instanceIdentifier, Long.valueOf(targetRotation));
verify(mockPreview).setTargetRotation(targetRotation);
}
}

View File

@ -6,7 +6,6 @@ package io.flutter.plugins.camerax;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
@ -16,20 +15,16 @@ import static org.mockito.Mockito.when;
import android.app.Activity;
import android.content.Context;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.camerax.CameraPermissionsManager.PermissionsRegistry;
import io.flutter.plugins.camerax.CameraPermissionsManager.ResultCallback;
import io.flutter.plugins.camerax.DeviceOrientationManager.DeviceOrientationChangeCallback;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraPermissionsErrorData;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Result;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi.Reply;
import java.io.File;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.MockitoJUnit;
@ -96,55 +91,6 @@ public class SystemServicesTest {
assertEquals(cameraPermissionsErrorData.getDescription(), testErrorDescription);
}
@Test
public void deviceOrientationChangeTest() {
final SystemServicesHostApiImpl systemServicesHostApi =
new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager, mockContext);
final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class);
final Activity mockActivity = mock(Activity.class);
final DeviceOrientationManager mockDeviceOrientationManager =
mock(DeviceOrientationManager.class);
final Boolean isFrontFacing = true;
final int sensorOrientation = 90;
SystemServicesFlutterApiImpl systemServicesFlutterApi =
mock(SystemServicesFlutterApiImpl.class);
systemServicesHostApi.systemServicesFlutterApi = systemServicesFlutterApi;
systemServicesHostApi.cameraXProxy = mockCameraXProxy;
systemServicesHostApi.setActivity(mockActivity);
when(mockCameraXProxy.createDeviceOrientationManager(
eq(mockActivity),
eq(isFrontFacing),
eq(sensorOrientation),
any(DeviceOrientationChangeCallback.class)))
.thenReturn(mockDeviceOrientationManager);
final ArgumentCaptor<DeviceOrientationChangeCallback> deviceOrientationChangeCallbackCaptor =
ArgumentCaptor.forClass(DeviceOrientationChangeCallback.class);
systemServicesHostApi.startListeningForDeviceOrientationChange(
isFrontFacing, Long.valueOf(sensorOrientation));
// Test callback method defined in Flutter API is called when device orientation changes.
verify(mockCameraXProxy)
.createDeviceOrientationManager(
eq(mockActivity),
eq(isFrontFacing),
eq(sensorOrientation),
deviceOrientationChangeCallbackCaptor.capture());
DeviceOrientationChangeCallback deviceOrientationChangeCallback =
deviceOrientationChangeCallbackCaptor.getValue();
deviceOrientationChangeCallback.onChange(DeviceOrientation.PORTRAIT_DOWN);
verify(systemServicesFlutterApi)
.sendDeviceOrientationChangedEvent(
eq(DeviceOrientation.PORTRAIT_DOWN.toString()), ArgumentMatchers.<Reply<Void>>any());
// Test that the DeviceOrientationManager starts listening for device orientation changes.
verify(mockDeviceOrientationManager).start();
}
@Test
public void getTempFilePath_returnsCorrectPath() {
final SystemServicesHostApiImpl systemServicesHostApi =

View File

@ -10,6 +10,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.view.Surface;
import androidx.camera.video.Recorder;
import androidx.camera.video.VideoCapture;
import io.flutter.plugin.common.BinaryMessenger;
@ -78,6 +79,20 @@ public class VideoCaptureTest {
testInstanceManager.remove(videoCaptureId);
}
@Test
public void setTargetRotation_makesCallToSetTargetRotation() {
final VideoCaptureHostApiImpl hostApi =
new VideoCaptureHostApiImpl(mockBinaryMessenger, testInstanceManager);
final long instanceIdentifier = 62;
final int targetRotation = Surface.ROTATION_270;
testInstanceManager.addDartCreatedInstance(mockVideoCapture, instanceIdentifier);
hostApi.setTargetRotation(instanceIdentifier, Long.valueOf(targetRotation));
verify(mockVideoCapture).setTargetRotation(targetRotation);
}
@Test
public void flutterApiCreateTest() {
final VideoCaptureFlutterApiImpl spyVideoCaptureFlutterApi =

View File

@ -296,14 +296,16 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
IconButton(
icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute),
color: Colors.blue,
onPressed: () {}, // TODO(camsim99): Add functionality back here.
onPressed: controller != null ? onAudioModeButtonPressed : null,
),
IconButton(
icon: Icon(controller?.value.isCaptureOrientationLocked ?? false
? Icons.screen_lock_rotation
: Icons.screen_rotation),
color: Colors.blue,
onPressed: () {}, // TODO(camsim99): Add functionality back here.
onPressed: controller != null
? onCaptureOrientationLockButtonPressed
: null,
),
],
),

View File

@ -6,6 +6,7 @@ import 'dart:async';
import 'package:async/async.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/services.dart' show DeviceOrientation;
import 'package:flutter/widgets.dart';
import 'package:stream_transform/stream_transform.dart';
@ -17,6 +18,7 @@ import 'camera_selector.dart';
import 'camera_state.dart';
import 'camerax_library.g.dart';
import 'camerax_proxy.dart';
import 'device_orientation_manager.dart';
import 'exposure_state.dart';
import 'fallback_strategy.dart';
import 'image_analysis.dart';
@ -155,6 +157,24 @@ class AndroidCameraCameraX extends CameraPlatform {
/// set for the camera in use.
static const String zoomStateNotSetErrorCode = 'zoomStateNotSet';
/// Whether or not the capture orientation is locked.
///
/// Indicates a new target rotation should not be set as it has been locked by
/// [lockCaptureOrientation].
@visibleForTesting
bool captureOrientationLocked = false;
/// Whether or not the default rotation for [UseCase]s needs to be set
/// manually because the capture orientation was previously locked.
///
/// Currently, CameraX provides no way to unset target rotations for
/// [UseCase]s, so once they are set and unset, this plugin must start setting
/// the default orientation manually.
///
/// See https://developer.android.com/reference/androidx/camera/core/ImageCapture#setTargetRotation(int)
/// for an example on how setting target rotations for [UseCase]s works.
bool shouldSetDefaultRotation = false;
/// Returns list of all available cameras and their descriptions.
@override
Future<List<CameraDescription>> availableCameras() async {
@ -240,20 +260,19 @@ class AndroidCameraCameraX extends CameraPlatform {
processCameraProvider!.unbindAll();
// Configure Preview instance.
final int targetRotation =
_getTargetRotation(cameraDescription.sensorOrientation);
preview = proxy.createPreview(
targetRotation: targetRotation,
resolutionSelector: presetResolutionSelector);
preview = proxy.createPreview(presetResolutionSelector,
/* use CameraX default target rotation */ null);
final int flutterSurfaceTextureId =
await proxy.setPreviewSurfaceProvider(preview!);
// Configure ImageCapture instance.
imageCapture = proxy.createImageCapture(presetResolutionSelector);
imageCapture = proxy.createImageCapture(presetResolutionSelector,
/* use CameraX default target rotation */ null);
// Configure ImageAnalysis instance.
// Defaults to YUV_420_888 image format.
imageAnalysis = proxy.createImageAnalysis(presetResolutionSelector);
imageAnalysis = proxy.createImageAnalysis(presetResolutionSelector,
/* use CameraX default target rotation */ null);
// Configure VideoCapture and Recorder instances.
recorder = proxy.createRecorder(presetQualitySelector);
@ -370,6 +389,35 @@ class AndroidCameraCameraX extends CameraPlatform {
return _cameraEvents(cameraId).whereType<VideoRecordedEvent>();
}
/// Locks the capture orientation.
@override
Future<void> lockCaptureOrientation(
int cameraId,
DeviceOrientation orientation,
) async {
// Flag that (1) default rotation for UseCases will need to be set manually
// if orientation is ever unlocked and (2) the capture orientation is locked
// and should not be changed until unlocked.
shouldSetDefaultRotation = true;
captureOrientationLocked = true;
// Get target rotation based on locked orientation.
final int targetLockedRotation =
_getRotationConstantFromDeviceOrientation(orientation);
// Update UseCases to use target device orientation.
await imageCapture!.setTargetRotation(targetLockedRotation);
await imageAnalysis!.setTargetRotation(targetLockedRotation);
await videoCapture!.setTargetRotation(targetLockedRotation);
}
/// Unlocks the capture orientation.
@override
Future<void> unlockCaptureOrientation(int cameraId) async {
// Flag that default rotation should be set for UseCases as needed.
captureOrientationLocked = false;
}
/// Gets the minimum supported exposure offset for the selected camera in EV units.
///
/// [cameraId] not used.
@ -449,7 +497,8 @@ class AndroidCameraCameraX extends CameraPlatform {
/// The ui orientation changed.
@override
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {
return SystemServices.deviceOrientationChangedStreamController.stream;
return DeviceOrientationManager
.deviceOrientationChangedStreamController.stream;
}
/// Pause the active preview on the current frame for the selected camera.
@ -493,6 +542,7 @@ class AndroidCameraCameraX extends CameraPlatform {
/// [cameraId] is not used.
@override
Future<XFile> takePicture(int cameraId) async {
// Set flash mode.
if (_currentFlashMode != null) {
await imageCapture!.setFlashMode(_currentFlashMode!);
} else if (torchEnabled) {
@ -500,6 +550,14 @@ class AndroidCameraCameraX extends CameraPlatform {
// been enabled.
await imageCapture!.setFlashMode(ImageCapture.flashModeOff);
}
// Set target rotation to default CameraX rotation only if capture
// orientation not locked.
if (!captureOrientationLocked && shouldSetDefaultRotation) {
await imageCapture!
.setTargetRotation(await proxy.getDefaultDisplayRotation());
}
final String picturePath = await imageCapture!.takePicture();
return XFile(picturePath);
}
@ -582,6 +640,13 @@ class AndroidCameraCameraX extends CameraPlatform {
.bindToLifecycle(cameraSelector!, <UseCase>[videoCapture!]);
}
// Set target rotation to default CameraX rotation only if capture
// orientation not locked.
if (!captureOrientationLocked && shouldSetDefaultRotation) {
await videoCapture!
.setTargetRotation(await proxy.getDefaultDisplayRotation());
}
videoOutputPath =
await SystemServices.getTempFilePath(videoPrefix, '.temp');
pendingRecording = await recorder!.prepareRecording(videoOutputPath!);
@ -654,7 +719,7 @@ class AndroidCameraCameraX extends CameraPlatform {
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
{CameraImageStreamOptions? options}) {
cameraImageDataStreamController = StreamController<CameraImageData>(
onListen: () => _onFrameStreamListen(cameraId),
onListen: () => _configureImageAnalysis(cameraId),
onCancel: _onFrameStreamCancel,
);
return cameraImageDataStreamController!.stream;
@ -683,7 +748,14 @@ class AndroidCameraCameraX extends CameraPlatform {
/// Configures the [imageAnalysis] instance for image streaming.
Future<void> _configureImageAnalysis(int cameraId) async {
// Create Analyzer that can read image data for image streaming.
// Set target rotation to default CameraX rotation only if capture
// orientation not locked.
if (!captureOrientationLocked && shouldSetDefaultRotation) {
await imageAnalysis!
.setTargetRotation(await proxy.getDefaultDisplayRotation());
}
// Create and set Analyzer that can read image data for image streaming.
final WeakReference<AndroidCameraCameraX> weakThis =
WeakReference<AndroidCameraCameraX>(this);
Future<void> analyze(ImageProxy imageProxy) async {
@ -708,7 +780,7 @@ class AndroidCameraCameraX extends CameraPlatform {
width: imageProxy.width);
weakThis.target!.cameraImageDataStreamController!.add(cameraImageData);
unawaited(imageProxy.close());
await imageProxy.close();
}
final Analyzer analyzer = proxy.createAnalyzer(analyze);
@ -728,12 +800,6 @@ class AndroidCameraCameraX extends CameraPlatform {
// Methods for configuring image streaming:
/// The [onListen] callback for the stream controller used for image
/// streaming.
Future<void> _onFrameStreamListen(int cameraId) async {
await _configureImageAnalysis(cameraId);
}
/// The [onCancel] callback for the stream controller used for image
/// streaming.
///
@ -816,21 +882,19 @@ class AndroidCameraCameraX extends CameraPlatform {
}
}
/// Returns [Surface] target rotation constant that maps to specified sensor
/// orientation.
int _getTargetRotation(int sensorOrientation) {
switch (sensorOrientation) {
case 90:
return Surface.ROTATION_90;
case 180:
return Surface.ROTATION_180;
case 270:
return Surface.ROTATION_270;
case 0:
/// Returns [Surface] constant for counter-clockwise degrees of rotation from
/// [DeviceOrientation.portraitUp] required to reach the specified
/// [DeviceOrientation].
int _getRotationConstantFromDeviceOrientation(DeviceOrientation orientation) {
switch (orientation) {
case DeviceOrientation.portraitUp:
return Surface.ROTATION_0;
default:
throw ArgumentError(
'"$sensorOrientation" is not a valid sensor orientation value');
case DeviceOrientation.landscapeLeft:
return Surface.ROTATION_90;
case DeviceOrientation.portraitDown:
return Surface.ROTATION_180;
case DeviceOrientation.landscapeRight:
return Surface.ROTATION_270;
}
}

View File

@ -10,6 +10,7 @@ import 'camera_selector.dart';
import 'camera_state.dart';
import 'camera_state_error.dart';
import 'camerax_library.g.dart';
import 'device_orientation_manager.dart';
import 'exposure_state.dart';
import 'image_proxy.dart';
import 'java_object.dart';
@ -34,6 +35,8 @@ class AndroidCameraXCameraFlutterApis {
CameraSelectorFlutterApiImpl? cameraSelectorFlutterApiImpl,
ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApiImpl,
SystemServicesFlutterApiImpl? systemServicesFlutterApiImpl,
DeviceOrientationManagerFlutterApiImpl?
deviceOrientationManagerFlutterApiImpl,
CameraStateErrorFlutterApiImpl? cameraStateErrorFlutterApiImpl,
CameraStateFlutterApiImpl? cameraStateFlutterApiImpl,
PendingRecordingFlutterApiImpl? pendingRecordingFlutterApiImpl,
@ -60,6 +63,9 @@ class AndroidCameraXCameraFlutterApis {
this.cameraFlutterApiImpl = cameraFlutterApiImpl ?? CameraFlutterApiImpl();
this.systemServicesFlutterApiImpl =
systemServicesFlutterApiImpl ?? SystemServicesFlutterApiImpl();
this.deviceOrientationManagerFlutterApiImpl =
deviceOrientationManagerFlutterApiImpl ??
DeviceOrientationManagerFlutterApiImpl();
this.cameraStateErrorFlutterApiImpl =
cameraStateErrorFlutterApiImpl ?? CameraStateErrorFlutterApiImpl();
this.cameraStateFlutterApiImpl =
@ -117,6 +123,10 @@ class AndroidCameraXCameraFlutterApis {
/// Flutter Api implementation for [SystemServices].
late final SystemServicesFlutterApiImpl systemServicesFlutterApiImpl;
/// Flutter Api implementation for [DeviceOrientationManager].
late final DeviceOrientationManagerFlutterApiImpl
deviceOrientationManagerFlutterApiImpl;
/// Flutter Api implementation for [CameraStateError].
late final CameraStateErrorFlutterApiImpl? cameraStateErrorFlutterApiImpl;
@ -169,6 +179,8 @@ class AndroidCameraXCameraFlutterApis {
processCameraProviderFlutterApiImpl);
CameraFlutterApi.setup(cameraFlutterApiImpl);
SystemServicesFlutterApi.setup(systemServicesFlutterApiImpl);
DeviceOrientationManagerFlutterApi.setup(
deviceOrientationManagerFlutterApiImpl);
CameraStateErrorFlutterApi.setup(cameraStateErrorFlutterApiImpl);
CameraStateFlutterApi.setup(cameraStateFlutterApiImpl);
PendingRecordingFlutterApi.setup(pendingRecordingFlutterApiImpl);

View File

@ -894,53 +894,6 @@ class SystemServicesHostApi {
}
}
Future<void> startListeningForDeviceOrientationChange(
bool arg_isFrontFacing, int arg_sensorOrientation) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_isFrontFacing, arg_sensorOrientation])
as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<void> stopListeningForDeviceOrientationChange() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<String> getTempFilePath(String arg_prefix, String arg_suffix) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.SystemServicesHostApi.getTempFilePath', codec,
@ -972,32 +925,10 @@ class SystemServicesHostApi {
abstract class SystemServicesFlutterApi {
static const MessageCodec<Object?> codec = StandardMessageCodec();
void onDeviceOrientationChanged(String orientation);
void onCameraError(String errorDescription);
static void setup(SystemServicesFlutterApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMessageHandler(null);
} else {
channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_orientation = (args[0] as String?);
assert(arg_orientation != null,
'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged was null, expected non-null String.');
api.onDeviceOrientationChanged(arg_orientation!);
return;
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError', codec,
@ -1020,6 +951,121 @@ abstract class SystemServicesFlutterApi {
}
}
class DeviceOrientationManagerHostApi {
/// Constructor for [DeviceOrientationManagerHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
DeviceOrientationManagerHostApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;
static const MessageCodec<Object?> codec = StandardMessageCodec();
Future<void> startListeningForDeviceOrientationChange(
bool arg_isFrontFacing, int arg_sensorOrientation) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.DeviceOrientationManagerHostApi.startListeningForDeviceOrientationChange',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_isFrontFacing, arg_sensorOrientation])
as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<void> stopListeningForDeviceOrientationChange() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.DeviceOrientationManagerHostApi.stopListeningForDeviceOrientationChange',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
Future<int> getDefaultDisplayRotation() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.DeviceOrientationManagerHostApi.getDefaultDisplayRotation',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as int?)!;
}
}
}
abstract class DeviceOrientationManagerFlutterApi {
static const MessageCodec<Object?> codec = StandardMessageCodec();
void onDeviceOrientationChanged(String orientation);
static void setup(DeviceOrientationManagerFlutterApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.DeviceOrientationManagerFlutterApi.onDeviceOrientationChanged',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMessageHandler(null);
} else {
channel.setMessageHandler((Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.DeviceOrientationManagerFlutterApi.onDeviceOrientationChanged was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_orientation = (args[0] as String?);
assert(arg_orientation != null,
'Argument for dev.flutter.pigeon.DeviceOrientationManagerFlutterApi.onDeviceOrientationChanged was null, expected non-null String.');
api.onDeviceOrientationChanged(arg_orientation!);
return;
});
}
}
}
}
class _PreviewHostApiCodec extends StandardMessageCodec {
const _PreviewHostApiCodec();
@override
@ -1151,6 +1197,28 @@ class PreviewHostApi {
return (replyList[0] as ResolutionInfo?)!;
}
}
Future<void> setTargetRotation(int arg_identifier, int arg_rotation) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.PreviewHostApi.setTargetRotation', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel
.send(<Object?>[arg_identifier, arg_rotation]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}
class VideoCaptureHostApi {
@ -1216,6 +1284,28 @@ class VideoCaptureHostApi {
return (replyList[0] as int?)!;
}
}
Future<void> setTargetRotation(int arg_identifier, int arg_rotation) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.VideoCaptureHostApi.setTargetRotation', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel
.send(<Object?>[arg_identifier, arg_rotation]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}
abstract class VideoCaptureFlutterApi {
@ -1603,14 +1693,17 @@ class ImageCaptureHostApi {
static const MessageCodec<Object?> codec = StandardMessageCodec();
Future<void> create(int arg_identifier, int? arg_flashMode,
int? arg_resolutionSelectorId) async {
Future<void> create(int arg_identifier, int? arg_targetRotation,
int? arg_flashMode, int? arg_resolutionSelectorId) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.ImageCaptureHostApi.create', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(
<Object?>[arg_identifier, arg_flashMode, arg_resolutionSelectorId])
as List<Object?>?;
final List<Object?>? replyList = await channel.send(<Object?>[
arg_identifier,
arg_targetRotation,
arg_flashMode,
arg_resolutionSelectorId
]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
@ -1675,6 +1768,28 @@ class ImageCaptureHostApi {
return (replyList[0] as String?)!;
}
}
Future<void> setTargetRotation(int arg_identifier, int arg_rotation) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.ImageCaptureHostApi.setTargetRotation', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel
.send(<Object?>[arg_identifier, arg_rotation]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}
class _ResolutionStrategyHostApiCodec extends StandardMessageCodec {
@ -1974,13 +2089,16 @@ class ImageAnalysisHostApi {
static const MessageCodec<Object?> codec = StandardMessageCodec();
Future<void> create(int arg_identifier, int? arg_resolutionSelectorId) async {
Future<void> create(int arg_identifier, int? arg_targetRotation,
int? arg_resolutionSelectorId) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.ImageAnalysisHostApi.create', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_identifier, arg_resolutionSelectorId])
as List<Object?>?;
final List<Object?>? replyList = await channel.send(<Object?>[
arg_identifier,
arg_targetRotation,
arg_resolutionSelectorId
]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
@ -2042,6 +2160,28 @@ class ImageAnalysisHostApi {
return;
}
}
Future<void> setTargetRotation(int arg_identifier, int arg_rotation) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.ImageAnalysisHostApi.setTargetRotation', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel
.send(<Object?>[arg_identifier, arg_rotation]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}
class AnalyzerHostApi {

View File

@ -8,6 +8,7 @@ import 'analyzer.dart';
import 'camera_selector.dart';
import 'camera_state.dart';
import 'camerax_library.g.dart';
import 'device_orientation_manager.dart';
import 'fallback_strategy.dart';
import 'image_analysis.dart';
import 'image_capture.dart';
@ -47,6 +48,7 @@ class CameraXProxy {
this.startListeningForDeviceOrientationChange =
_startListeningForDeviceOrientationChange,
this.setPreviewSurfaceProvider = _setPreviewSurfaceProvider,
this.getDefaultDisplayRotation = _getDefaultDisplayRotation,
});
/// Returns a [ProcessCameraProvider] instance.
@ -58,12 +60,14 @@ class CameraXProxy {
/// Returns a [Preview] configured with the specified target rotation and
/// specified [ResolutionSelector].
Preview Function(
{required int targetRotation,
ResolutionSelector? resolutionSelector}) createPreview;
ResolutionSelector? resolutionSelector,
int? targetRotation,
) createPreview;
/// Returns an [ImageCapture] configured with specified flash mode and
/// the specified [ResolutionSelector].
ImageCapture Function(ResolutionSelector? resolutionSelector)
ImageCapture Function(
ResolutionSelector? resolutionSelector, int? targetRotation)
createImageCapture;
/// Returns a [Recorder] for use in video capture configured with the
@ -75,7 +79,8 @@ class CameraXProxy {
/// Returns an [ImageAnalysis] configured with the specified
/// [ResolutionSelector].
ImageAnalysis Function(ResolutionSelector? resolutionSelector)
ImageAnalysis Function(
ResolutionSelector? resolutionSelector, int? targetRotation)
createImageAnalysis;
/// Returns an [Analyzer] configured with the specified callback for
@ -128,6 +133,10 @@ class CameraXProxy {
/// the ID corresponding to the surface it will provide.
Future<int> Function(Preview preview) setPreviewSurfaceProvider;
/// Returns default rotation for [UseCase]s in terms of one of the [Surface]
/// rotation constants.
Future<int> Function() getDefaultDisplayRotation;
static Future<ProcessCameraProvider> _getProcessCameraProvider() {
return ProcessCameraProvider.getInstance();
}
@ -145,14 +154,17 @@ class CameraXProxy {
}
static Preview _createAttachedPreview(
{required int targetRotation, ResolutionSelector? resolutionSelector}) {
ResolutionSelector? resolutionSelector, int? targetRotation) {
return Preview(
targetRotation: targetRotation, resolutionSelector: resolutionSelector);
initialTargetRotation: targetRotation,
resolutionSelector: resolutionSelector);
}
static ImageCapture _createAttachedImageCapture(
ResolutionSelector? resolutionSelector) {
return ImageCapture(resolutionSelector: resolutionSelector);
ResolutionSelector? resolutionSelector, int? targetRotation) {
return ImageCapture(
resolutionSelector: resolutionSelector,
initialTargetRotation: targetRotation);
}
static Recorder _createAttachedRecorder(QualitySelector? qualitySelector) {
@ -165,8 +177,10 @@ class CameraXProxy {
}
static ImageAnalysis _createAttachedImageAnalysis(
ResolutionSelector? resolutionSelector) {
return ImageAnalysis(resolutionSelector: resolutionSelector);
ResolutionSelector? resolutionSelector, int? targetRotation) {
return ImageAnalysis(
resolutionSelector: resolutionSelector,
initialTargetRotation: targetRotation);
}
static Analyzer _createAttachedAnalyzer(
@ -214,11 +228,15 @@ class CameraXProxy {
static void _startListeningForDeviceOrientationChange(
bool cameraIsFrontFacing, int sensorOrientation) {
SystemServices.startListeningForDeviceOrientationChange(
DeviceOrientationManager.startListeningForDeviceOrientationChange(
cameraIsFrontFacing, sensorOrientation);
}
static Future<int> _setPreviewSurfaceProvider(Preview preview) async {
return preview.setSurfaceProvider();
}
static Future<int> _getDefaultDisplayRotation() async {
return DeviceOrientationManager.getDefaultDisplayRotation();
}
}

View File

@ -0,0 +1,121 @@
// 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.
import 'dart:async';
import 'package:camera_platform_interface/camera_platform_interface.dart'
show DeviceOrientationChangedEvent;
import 'package:flutter/services.dart';
import 'android_camera_camerax_flutter_api_impls.dart';
import 'camerax_library.g.dart';
// Ignoring lint indicating this class only contains static members
// as this class is a wrapper for various Android system services.
// ignore_for_file: avoid_classes_with_only_static_members
/// Utility class that offers access to Android system services needed for
/// camera usage and other informational streams.
class DeviceOrientationManager {
/// Stream that emits the device orientation whenever it is changed.
///
/// Values may start being added to the stream once
/// `startListeningForDeviceOrientationChange(...)` is called.
static final StreamController<DeviceOrientationChangedEvent>
deviceOrientationChangedStreamController =
StreamController<DeviceOrientationChangedEvent>.broadcast();
/// Requests that [deviceOrientationChangedStreamController] start
/// emitting values for any change in device orientation.
static void startListeningForDeviceOrientationChange(
bool isFrontFacing, int sensorOrientation,
{BinaryMessenger? binaryMessenger}) {
AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
final DeviceOrientationManagerHostApi api =
DeviceOrientationManagerHostApi(binaryMessenger: binaryMessenger);
api.startListeningForDeviceOrientationChange(
isFrontFacing, sensorOrientation);
}
/// Stops the [deviceOrientationChangedStreamController] from emitting values
/// for changes in device orientation.
static void stopListeningForDeviceOrientationChange(
{BinaryMessenger? binaryMessenger}) {
final DeviceOrientationManagerHostApi api =
DeviceOrientationManagerHostApi(binaryMessenger: binaryMessenger);
api.stopListeningForDeviceOrientationChange();
}
/// Retrieves the default rotation that CameraX uses for [UseCase]s in terms
/// of one of the [Surface] rotation constants.
///
/// The default rotation that CameraX uses is the rotation of the default
/// display at the time of binding a particular [UseCase], but the default
/// display does not change in the plugin, so this default value is
/// display-agnostic.
///
/// [startListeningForDeviceOrientationChange] must be called before calling
/// this method.
static Future<int> getDefaultDisplayRotation(
{BinaryMessenger? binaryMessenger}) async {
final DeviceOrientationManagerHostApi api =
DeviceOrientationManagerHostApi(binaryMessenger: binaryMessenger);
return api.getDefaultDisplayRotation();
}
/// Serializes [DeviceOrientation] into a [String].
static String serializeDeviceOrientation(DeviceOrientation orientation) {
switch (orientation) {
case DeviceOrientation.landscapeLeft:
return 'LANDSCAPE_LEFT';
case DeviceOrientation.landscapeRight:
return 'LANDSCAPE_RIGHT';
case DeviceOrientation.portraitDown:
return 'PORTRAIT_DOWN';
case DeviceOrientation.portraitUp:
return 'PORTRAIT_UP';
}
}
}
/// Flutter API implementation of [DeviceOrientationManager].
class DeviceOrientationManagerFlutterApiImpl
implements DeviceOrientationManagerFlutterApi {
/// Constructs an [DeviceOrientationManagerFlutterApiImpl].
DeviceOrientationManagerFlutterApiImpl();
/// Callback method for any changes in device orientation.
///
/// Will only be called if
/// `DeviceOrientationManager.startListeningForDeviceOrientationChange(...)` was called
/// to start listening for device orientation updates.
@override
void onDeviceOrientationChanged(String orientation) {
final DeviceOrientation deviceOrientation =
deserializeDeviceOrientation(orientation);
DeviceOrientationManager.deviceOrientationChangedStreamController
.add(DeviceOrientationChangedEvent(deviceOrientation));
}
/// Deserializes device orientation in [String] format into a
/// [DeviceOrientation].
DeviceOrientation deserializeDeviceOrientation(String orientation) {
switch (orientation) {
case 'LANDSCAPE_LEFT':
return DeviceOrientation.landscapeLeft;
case 'LANDSCAPE_RIGHT':
return DeviceOrientation.landscapeRight;
case 'PORTRAIT_DOWN':
return DeviceOrientation.portraitDown;
case 'PORTRAIT_UP':
return DeviceOrientation.portraitUp;
default:
throw ArgumentError(
'"$orientation" is not a valid DeviceOrientation value');
}
}
}

View File

@ -24,20 +24,23 @@ class ImageAnalysis extends UseCase {
ImageAnalysis(
{BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
this.initialTargetRotation,
this.resolutionSelector})
: super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager) {
_api = _ImageAnalysisHostApiImpl(
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
_api.createfromInstances(this, resolutionSelector);
_api.createFromInstances(this, initialTargetRotation, resolutionSelector);
AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
}
/// Constructs an [ImageAnalysis] that is not automatically attached to a native object.
/// Constructs an [ImageAnalysis] that is not automatically attached to a
/// native object.
ImageAnalysis.detached(
{BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
this.initialTargetRotation,
this.resolutionSelector})
: super.detached(
binaryMessenger: binaryMessenger,
@ -49,18 +52,34 @@ class ImageAnalysis extends UseCase {
late final _ImageAnalysisHostApiImpl _api;
/// Initial target rotation of the camera used for the preview stream.
///
/// Should be specified in terms of one of the [Surface]
/// rotation constants that represents the counter-clockwise degrees of
/// rotation relative to [DeviceOrientation.portraitUp].
// TODO(camsim99): Remove this parameter. https://github.com/flutter/flutter/issues/140664
final int? initialTargetRotation;
/// Target resolution of the camera preview stream.
///
/// If not set, this [UseCase] will default to the behavior described in:
/// https://developer.android.com/reference/androidx/camera/core/ImageAnalysis.Builder#setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector).
final ResolutionSelector? resolutionSelector;
/// Dynamically sets the target rotation of this instance.
///
/// [rotation] should be specified in terms of one of the [Surface]
/// rotation constants that represents the counter-clockwise degrees of
/// rotation relative to [DeviceOrientation.portraitUp].
Future<void> setTargetRotation(int rotation) =>
_api.setTargetRotationFromInstances(this, rotation);
/// Sets an [Analyzer] to receive and analyze images.
Future<void> setAnalyzer(Analyzer analyzer) =>
_api.setAnalyzerfromInstances(this, analyzer);
_api.setAnalyzerFromInstances(this, analyzer);
/// Removes a previously set [Analyzer].
Future<void> clearAnalyzer() => _api.clearAnalyzerfromInstances(this);
Future<void> clearAnalyzer() => _api.clearAnalyzerFromInstances(this);
}
/// Host API implementation of [ImageAnalysis].
@ -85,27 +104,37 @@ class _ImageAnalysisHostApiImpl extends ImageAnalysisHostApi {
/// Creates an [ImageAnalysis] instance with the specified target resolution
/// on the native side.
Future<void> createfromInstances(
Future<void> createFromInstances(
ImageAnalysis instance,
int? targetRotation,
ResolutionSelector? resolutionSelector,
) {
return create(
instanceManager.addDartCreatedInstance(
instance,
onCopy: (ImageAnalysis original) => ImageAnalysis.detached(
initialTargetRotation: original.initialTargetRotation,
resolutionSelector: original.resolutionSelector,
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
),
),
targetRotation,
resolutionSelector == null
? null
: instanceManager.getIdentifier(resolutionSelector),
);
}
/// Dynamically sets the target rotation of [instance] to [rotation].
Future<void> setTargetRotationFromInstances(
ImageAnalysis instance, int rotation) {
return setTargetRotation(
instanceManager.getIdentifier(instance)!, rotation);
}
/// Sets the [analyzer] to receive and analyze images on the [instance].
Future<void> setAnalyzerfromInstances(
Future<void> setAnalyzerFromInstances(
ImageAnalysis instance,
Analyzer analyzer,
) {
@ -116,7 +145,7 @@ class _ImageAnalysisHostApiImpl extends ImageAnalysisHostApi {
}
/// Removes a previously set analyzer from the [instance].
Future<void> clearAnalyzerfromInstances(
Future<void> clearAnalyzerFromInstances(
ImageAnalysis instance,
) {
return clearAnalyzer(

View File

@ -20,6 +20,7 @@ class ImageCapture extends UseCase {
ImageCapture({
BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
this.initialTargetRotation,
this.targetFlashMode,
this.resolutionSelector,
}) : super.detached(
@ -28,13 +29,16 @@ class ImageCapture extends UseCase {
) {
_api = ImageCaptureHostApiImpl(
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
_api.createFromInstance(this, targetFlashMode, resolutionSelector);
_api.createFromInstance(
this, initialTargetRotation, targetFlashMode, resolutionSelector);
}
/// Constructs a [ImageCapture] that is not automatically attached to a native object.
/// Constructs an [ImageCapture] that is not automatically attached to a
/// native object.
ImageCapture.detached({
BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
this.initialTargetRotation,
this.targetFlashMode,
this.resolutionSelector,
}) : super.detached(
@ -47,6 +51,15 @@ class ImageCapture extends UseCase {
late final ImageCaptureHostApiImpl _api;
/// Initial target rotation of the camera used for the preview stream.
///
/// Should be specified in terms of one of the [Surface]
/// rotation constants that represents the counter-clockwise degrees of
/// rotation relative to [DeviceOrientation.portraitUp].
///
// TODO(camsim99): Remove this parameter. https://github.com/flutter/flutter/issues/140664
final int? initialTargetRotation;
/// Flash mode used to take a picture.
final int? targetFlashMode;
@ -71,9 +84,17 @@ class ImageCapture extends UseCase {
/// See https://developer.android.com/reference/androidx/camera/core/ImageCapture#FLASH_MODE_OFF().
static const int flashModeOff = 2;
/// Dynamically sets the target rotation of this instance.
///
/// [rotation] should be specified in terms of one of the [Surface]
/// rotation constants that represents the counter-clockwise degrees of
/// rotation relative to [DeviceOrientation.portraitUp].
Future<void> setTargetRotation(int rotation) =>
_api.setTargetRotationFromInstances(this, rotation);
/// Sets the flash mode to use for image capture.
Future<void> setFlashMode(int newFlashMode) async {
return _api.setFlashModeFromInstance(this, newFlashMode);
return _api.setFlashModeFromInstances(this, newFlashMode);
}
/// Takes a picture and returns the absolute path of where the capture image
@ -94,7 +115,7 @@ class ImageCapture extends UseCase {
/// See https://developer.android.com/reference/androidx/camera/core/ImageCapture
/// for more information.
Future<String> takePicture() async {
return _api.takePictureFromInstance(this);
return _api.takePictureFromInstances(this);
}
}
@ -124,27 +145,36 @@ class ImageCaptureHostApiImpl extends ImageCaptureHostApi {
/// Creates an [ImageCapture] instance with the flash mode and target resolution
/// if specified.
void createFromInstance(ImageCapture instance, int? targetFlashMode,
ResolutionSelector? resolutionSelector) {
void createFromInstance(ImageCapture instance, int? targetRotation,
int? targetFlashMode, ResolutionSelector? resolutionSelector) {
final int identifier = instanceManager.addDartCreatedInstance(instance,
onCopy: (ImageCapture original) {
return ImageCapture.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
initialTargetRotation: original.initialTargetRotation,
targetFlashMode: original.targetFlashMode,
resolutionSelector: original.resolutionSelector);
});
create(
identifier,
targetRotation,
targetFlashMode,
resolutionSelector == null
? null
: instanceManager.getIdentifier(resolutionSelector));
}
/// Dynamically sets the target rotation of [instance] to [rotation].
Future<void> setTargetRotationFromInstances(
ImageCapture instance, int rotation) {
return setTargetRotation(
instanceManager.getIdentifier(instance)!, rotation);
}
/// Sets the flash mode for the specified [ImageCapture] instance to take
/// a picture with.
Future<void> setFlashModeFromInstance(
Future<void> setFlashModeFromInstances(
ImageCapture instance, int flashMode) async {
final int? identifier = instanceManager.getIdentifier(instance);
assert(identifier != null,
@ -154,7 +184,7 @@ class ImageCaptureHostApiImpl extends ImageCaptureHostApi {
}
/// Takes a picture with the specified [ImageCapture] instance.
Future<String> takePictureFromInstance(ImageCapture instance) async {
Future<String> takePictureFromInstances(ImageCapture instance) async {
final int? identifier = instanceManager.getIdentifier(instance);
assert(identifier != null,
'No ImageCapture has the identifer of that requested to get the resolution information for.');

View File

@ -20,21 +20,21 @@ class Preview extends UseCase {
Preview(
{BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
this.targetRotation,
this.initialTargetRotation,
this.resolutionSelector})
: super.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager) {
_api = PreviewHostApiImpl(
binaryMessenger: binaryMessenger, instanceManager: instanceManager);
_api.createFromInstance(this, targetRotation, resolutionSelector);
_api.createFromInstance(this, initialTargetRotation, resolutionSelector);
}
/// Constructs a [Preview] that is not automatically attached to a native object.
Preview.detached(
{BinaryMessenger? binaryMessenger,
InstanceManager? instanceManager,
this.targetRotation,
this.initialTargetRotation,
this.resolutionSelector})
: super.detached(
binaryMessenger: binaryMessenger,
@ -46,7 +46,13 @@ class Preview extends UseCase {
late final PreviewHostApiImpl _api;
/// Target rotation of the camera used for the preview stream.
final int? targetRotation;
///
/// Should be specified in terms of one of the [Surface]
/// rotation constants that represents the counter-clockwise degrees of
/// rotation relative to [DeviceOrientation.portraitUp].
///
// TODO(camsim99): Remove this parameter. https://github.com/flutter/flutter/issues/140664
final int? initialTargetRotation;
/// Target resolution of the camera preview stream.
///
@ -54,6 +60,14 @@ class Preview extends UseCase {
/// https://developer.android.com/reference/androidx/camera/core/Preview.Builder#setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector).
final ResolutionSelector? resolutionSelector;
/// Dynamically sets the target rotation of this instance.
///
/// [rotation] should be specified in terms of one of the [Surface]
/// rotation constants that represents the counter-clockwise degrees of
/// rotation relative to [DeviceOrientation.portraitUp].
Future<void> setTargetRotation(int rotation) =>
_api.setTargetRotationFromInstances(this, rotation);
/// Sets the surface provider for the preview stream.
///
/// Returns the ID of the FlutterSurfaceTextureEntry used on the native end
@ -103,7 +117,7 @@ class PreviewHostApiImpl extends PreviewHostApi {
return Preview.detached(
binaryMessenger: binaryMessenger,
instanceManager: instanceManager,
targetRotation: original.targetRotation,
initialTargetRotation: original.initialTargetRotation,
resolutionSelector: original.resolutionSelector);
});
create(
@ -114,6 +128,12 @@ class PreviewHostApiImpl extends PreviewHostApi {
: instanceManager.getIdentifier(resolutionSelector));
}
/// Dynamically sets the target rotation of [instance] to [rotation].
Future<void> setTargetRotationFromInstances(Preview instance, int rotation) {
return setTargetRotation(
instanceManager.getIdentifier(instance)!, rotation);
}
/// Sets the surface provider of the specified [Preview] instance and returns
/// the ID corresponding to the surface it will provide.
Future<int> setSurfaceProviderFromInstance(Preview instance) async {

View File

@ -5,10 +5,9 @@
import 'dart:async';
import 'package:camera_platform_interface/camera_platform_interface.dart'
show CameraException, DeviceOrientationChangedEvent;
show CameraException;
import 'package:flutter/services.dart';
import 'android_camera_camerax_flutter_api_impls.dart';
import 'camerax_library.g.dart';
// Ignoring lint indicating this class only contains static members
@ -18,14 +17,6 @@ import 'camerax_library.g.dart';
/// Utility class that offers access to Android system services needed for
/// camera usage and other informational streams.
class SystemServices {
/// Stream that emits the device orientation whenever it is changed.
///
/// Values may start being added to the stream once
/// `startListeningForDeviceOrientationChange(...)` is called.
static final StreamController<DeviceOrientationChangedEvent>
deviceOrientationChangedStreamController =
StreamController<DeviceOrientationChangedEvent>.broadcast();
/// Stream that emits the errors caused by camera usage on the native side.
static final StreamController<String> cameraErrorStreamController =
StreamController<String>.broadcast();
@ -39,29 +30,6 @@ class SystemServices {
return api.sendCameraPermissionsRequest(enableAudio);
}
/// Requests that [deviceOrientationChangedStreamController] start
/// emitting values for any change in device orientation.
static void startListeningForDeviceOrientationChange(
bool isFrontFacing, int sensorOrientation,
{BinaryMessenger? binaryMessenger}) {
AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
final SystemServicesHostApi api =
SystemServicesHostApi(binaryMessenger: binaryMessenger);
api.startListeningForDeviceOrientationChange(
isFrontFacing, sensorOrientation);
}
/// Stops the [deviceOrientationChangedStreamController] from emitting values
/// for changes in device orientation.
static void stopListeningForDeviceOrientationChange(
{BinaryMessenger? binaryMessenger}) {
final SystemServicesHostApi api =
SystemServicesHostApi(binaryMessenger: binaryMessenger);
api.stopListeningForDeviceOrientationChange();
}
/// Returns a file path which was used to create a temporary file.
/// Prefix is a part of the file name, and suffix is the file extension.
///
@ -116,37 +84,6 @@ class SystemServicesFlutterApiImpl implements SystemServicesFlutterApi {
/// Constructs an [SystemServicesFlutterApiImpl].
SystemServicesFlutterApiImpl();
/// Callback method for any changes in device orientation.
///
/// Will only be called if
/// `SystemServices.startListeningForDeviceOrientationChange(...)` was called
/// to start listening for device orientation updates.
@override
void onDeviceOrientationChanged(String orientation) {
final DeviceOrientation deviceOrientation =
deserializeDeviceOrientation(orientation);
SystemServices.deviceOrientationChangedStreamController
.add(DeviceOrientationChangedEvent(deviceOrientation));
}
/// Deserializes device orientation in [String] format into a
/// [DeviceOrientation].
DeviceOrientation deserializeDeviceOrientation(String orientation) {
switch (orientation) {
case 'LANDSCAPE_LEFT':
return DeviceOrientation.landscapeLeft;
case 'LANDSCAPE_RIGHT':
return DeviceOrientation.landscapeRight;
case 'PORTRAIT_DOWN':
return DeviceOrientation.portraitDown;
case 'PORTRAIT_UP':
return DeviceOrientation.portraitUp;
default:
throw ArgumentError(
'"$orientation" is not a valid DeviceOrientation value');
}
}
/// Callback method for any errors caused by camera usage on the Java side.
@override
void onCameraError(String errorDescription) {

View File

@ -17,7 +17,8 @@ import 'use_case.dart';
/// See https://developer.android.com/reference/androidx/camera/video/VideoCapture.
@immutable
class VideoCapture extends UseCase {
/// Creates a VideoCapture that is not automatically attached to a native object.
/// Creates a [VideoCapture] that is not automatically attached to a native
/// object.
VideoCapture.detached(
{BinaryMessenger? binaryMessenger, InstanceManager? instanceManager})
: super.detached(
@ -28,6 +29,8 @@ class VideoCapture extends UseCase {
AndroidCameraXCameraFlutterApis.instance.ensureSetUp();
}
late final VideoCaptureHostApiImpl _api;
/// Creates a [VideoCapture] associated with the given [Recorder].
static Future<VideoCapture> withOutput(Recorder recorder,
{BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) {
@ -38,12 +41,18 @@ class VideoCapture extends UseCase {
return api.withOutputFromInstance(recorder);
}
/// Dynamically sets the target rotation of this instance.
///
/// [rotation] should be specified in terms of one of the [Surface]
/// rotation constants that represents the counter-clockwise degrees of
/// rotation relative to [DeviceOrientation.portraitUp].
Future<void> setTargetRotation(int rotation) =>
_api.setTargetRotationFromInstances(this, rotation);
/// Gets the [Recorder] associated with this VideoCapture.
Future<Recorder> getOutput() {
return _api.getOutputFromInstance(this);
}
late final VideoCaptureHostApiImpl _api;
}
/// Host API implementation of [VideoCapture].
@ -76,6 +85,13 @@ class VideoCaptureHostApiImpl extends VideoCaptureHostApi {
.getInstanceWithWeakReference<VideoCapture>(videoCaptureId)!;
}
/// Dynamically sets the target rotation of [instance] to [rotation].
Future<void> setTargetRotationFromInstances(
VideoCapture instance, int rotation) {
return setTargetRotation(
instanceManager.getIdentifier(instance)!, rotation);
}
/// Gets the [Recorder] associated with the provided [VideoCapture] instance.
Future<Recorder> getOutputFromInstance(VideoCapture instance) async {
final int? identifier = instanceManager.getIdentifier(instance);

View File

@ -211,21 +211,29 @@ abstract class SystemServicesHostApi {
@async
CameraPermissionsErrorData? requestCameraPermissions(bool enableAudio);
void startListeningForDeviceOrientationChange(
bool isFrontFacing, int sensorOrientation);
void stopListeningForDeviceOrientationChange();
String getTempFilePath(String prefix, String suffix);
}
@FlutterApi()
abstract class SystemServicesFlutterApi {
void onDeviceOrientationChanged(String orientation);
void onCameraError(String errorDescription);
}
@HostApi(dartHostTestHandler: 'TestDeviceOrientationManagerHostApi')
abstract class DeviceOrientationManagerHostApi {
void startListeningForDeviceOrientationChange(
bool isFrontFacing, int sensorOrientation);
void stopListeningForDeviceOrientationChange();
int getDefaultDisplayRotation();
}
@FlutterApi()
abstract class DeviceOrientationManagerFlutterApi {
void onDeviceOrientationChanged(String orientation);
}
@HostApi(dartHostTestHandler: 'TestPreviewHostApi')
abstract class PreviewHostApi {
void create(int identifier, int? rotation, int? resolutionSelectorId);
@ -235,6 +243,8 @@ abstract class PreviewHostApi {
void releaseFlutterSurfaceTexture();
ResolutionInfo getResolutionInfo(int identifier);
void setTargetRotation(int identifier, int rotation);
}
@HostApi(dartHostTestHandler: 'TestVideoCaptureHostApi')
@ -242,6 +252,8 @@ abstract class VideoCaptureHostApi {
int withOutput(int videoOutputId);
int getOutput(int identifier);
void setTargetRotation(int identifier, int rotation);
}
@FlutterApi()
@ -294,12 +306,15 @@ abstract class RecordingFlutterApi {
@HostApi(dartHostTestHandler: 'TestImageCaptureHostApi')
abstract class ImageCaptureHostApi {
void create(int identifier, int? flashMode, int? resolutionSelectorId);
void create(int identifier, int? targetRotation, int? flashMode,
int? resolutionSelectorId);
void setFlashMode(int identifier, int flashMode);
@async
String takePicture(int identifier);
void setTargetRotation(int identifier, int rotation);
}
@HostApi(dartHostTestHandler: 'TestResolutionStrategyHostApi')
@ -341,11 +356,13 @@ abstract class ZoomStateFlutterApi {
@HostApi(dartHostTestHandler: 'TestImageAnalysisHostApi')
abstract class ImageAnalysisHostApi {
void create(int identifier, int? resolutionSelectorId);
void create(int identifier, int? targetRotation, int? resolutionSelectorId);
void setAnalyzer(int identifier, int analyzerIdentifier);
void clearAnalyzer(int identifier);
void setTargetRotation(int identifier, int rotation);
}
@HostApi(dartHostTestHandler: 'TestAnalyzerHostApi')

View File

@ -2,7 +2,7 @@ name: camera_android_camerax
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.5.0+24
version: 0.5.0+25
environment:
sdk: ">=3.0.0 <4.0.0"

View File

@ -15,6 +15,7 @@ import 'package:camera_android_camerax/src/camera_state.dart';
import 'package:camera_android_camerax/src/camera_state_error.dart';
import 'package:camera_android_camerax/src/camerax_library.g.dart';
import 'package:camera_android_camerax/src/camerax_proxy.dart';
import 'package:camera_android_camerax/src/device_orientation_manager.dart';
import 'package:camera_android_camerax/src/exposure_state.dart';
import 'package:camera_android_camerax/src/fallback_strategy.dart';
import 'package:camera_android_camerax/src/image_analysis.dart';
@ -31,6 +32,7 @@ import 'package:camera_android_camerax/src/recorder.dart';
import 'package:camera_android_camerax/src/recording.dart';
import 'package:camera_android_camerax/src/resolution_selector.dart';
import 'package:camera_android_camerax/src/resolution_strategy.dart';
import 'package:camera_android_camerax/src/surface.dart';
import 'package:camera_android_camerax/src/system_services.dart';
import 'package:camera_android_camerax/src/use_case.dart';
import 'package:camera_android_camerax/src/video_capture.dart';
@ -229,14 +231,11 @@ void main() {
return mockBackCameraSelector;
}
},
createPreview: (
{required int targetRotation,
ResolutionSelector? resolutionSelector}) =>
mockPreview,
createImageCapture: (_) => mockImageCapture,
createPreview: (_, __) => mockPreview,
createImageCapture: (_, __) => mockImageCapture,
createRecorder: (_) => mockRecorder,
createVideoCapture: (_) => Future<VideoCapture>.value(mockVideoCapture),
createImageAnalysis: (_) => mockImageAnalysis,
createImageAnalysis: (_, __) => mockImageAnalysis,
createResolutionStrategy: (
{bool highestAvailable = false,
Size? boundSize,
@ -347,14 +346,11 @@ void main() {
return mockBackCameraSelector;
}
},
createPreview: (
{required int targetRotation,
ResolutionSelector? resolutionSelector}) =>
mockPreview,
createImageCapture: (_) => mockImageCapture,
createPreview: (_, __) => mockPreview,
createImageCapture: (_, __) => mockImageCapture,
createRecorder: (_) => mockRecorder,
createVideoCapture: (_) => Future<VideoCapture>.value(mockVideoCapture),
createImageAnalysis: (_) => mockImageAnalysis,
createImageAnalysis: (_, __) => mockImageAnalysis,
createResolutionStrategy: (
{bool highestAvailable = false,
Size? boundSize,
@ -431,18 +427,23 @@ void main() {
return mockBackCameraSelector;
}
},
createPreview: (
{required int targetRotation,
ResolutionSelector? resolutionSelector}) =>
Preview.detached(
targetRotation: targetRotation,
resolutionSelector: resolutionSelector),
createImageCapture: (ResolutionSelector? resolutionSelector) =>
ImageCapture.detached(resolutionSelector: resolutionSelector),
createPreview:
(ResolutionSelector? resolutionSelector, int? targetRotation) =>
Preview.detached(
initialTargetRotation: targetRotation,
resolutionSelector: resolutionSelector),
createImageCapture:
(ResolutionSelector? resolutionSelector, int? targetRotation) =>
ImageCapture.detached(
resolutionSelector: resolutionSelector,
initialTargetRotation: targetRotation),
createRecorder: (_) => mockRecorder,
createVideoCapture: (_) => Future<VideoCapture>.value(mockVideoCapture),
createImageAnalysis: (ResolutionSelector? resolutionSelector) =>
ImageAnalysis.detached(resolutionSelector: resolutionSelector),
createImageAnalysis:
(ResolutionSelector? resolutionSelector, int? targetRotation) =>
ImageAnalysis.detached(
resolutionSelector: resolutionSelector,
initialTargetRotation: targetRotation),
createResolutionStrategy: (
{bool highestAvailable = false, Size? boundSize, int? fallbackRule}) {
if (highestAvailable) {
@ -581,15 +582,12 @@ void main() {
return mockBackCameraSelector;
}
},
createPreview: (
{required int targetRotation,
ResolutionSelector? resolutionSelector}) =>
mockPreview,
createImageCapture: (_) => mockImageCapture,
createPreview: (_, __) => mockPreview,
createImageCapture: (_, __) => mockImageCapture,
createRecorder: (QualitySelector? qualitySelector) =>
Recorder.detached(qualitySelector: qualitySelector),
createVideoCapture: (_) => Future<VideoCapture>.value(mockVideoCapture),
createImageAnalysis: (_) => mockImageAnalysis,
createImageAnalysis: (_, __) => mockImageAnalysis,
createResolutionStrategy: (
{bool highestAvailable = false,
Size? boundSize,
@ -716,14 +714,11 @@ void main() {
return mockBackCameraSelector;
}
},
createPreview: (
{required int targetRotation,
ResolutionSelector? resolutionSelector}) =>
mockPreview,
createImageCapture: (_) => mockImageCapture,
createPreview: (_, __) => mockPreview,
createImageCapture: (_, __) => mockImageCapture,
createRecorder: (QualitySelector? qualitySelector) => MockRecorder(),
createVideoCapture: (_) => Future<VideoCapture>.value(MockVideoCapture()),
createImageAnalysis: (_) => mockImageAnalysis,
createImageAnalysis: (_, __) => mockImageAnalysis,
createResolutionStrategy: (
{bool highestAvailable = false,
Size? boundSize,
@ -870,7 +865,8 @@ void main() {
const DeviceOrientationChangedEvent testEvent =
DeviceOrientationChangedEvent(DeviceOrientation.portraitDown);
SystemServices.deviceOrientationChangedStreamController.add(testEvent);
DeviceOrientationManager.deviceOrientationChangedStreamController
.add(testEvent);
expect(await streamQueue.next, testEvent);
await streamQueue.cancel();
@ -1084,6 +1080,9 @@ void main() {
camera.videoCapture = MockVideoCapture();
camera.cameraSelector = MockCameraSelector();
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
const int cameraId = 17;
const String outputPath = '/temp/MOV123.temp';
@ -1124,6 +1123,9 @@ void main() {
camera.videoCapture = MockVideoCapture();
camera.cameraSelector = MockCameraSelector();
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
const int cameraId = 17;
const String outputPath = '/temp/MOV123.temp';
@ -1178,6 +1180,9 @@ void main() {
MockTestSystemServicesHostApi();
TestSystemServicesHostApi.setup(mockSystemServicesApi);
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
// Tell plugin to create detached Analyzer for testing.
camera.proxy = CameraXProxy(
createAnalyzer:
@ -1214,6 +1219,73 @@ void main() {
await camera.cameraImageDataStreamController!.close();
});
test(
'startVideoCapturing sets VideoCapture target rotation to current video orientation if orientation unlocked',
() async {
// Set up mocks and constants.
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockPendingRecording mockPendingRecording = MockPendingRecording();
final MockRecording mockRecording = MockRecording();
final MockVideoCapture mockVideoCapture = MockVideoCapture();
final TestSystemServicesHostApi mockSystemServicesApi =
MockTestSystemServicesHostApi();
TestSystemServicesHostApi.setup(mockSystemServicesApi);
const int defaultTargetRotation = Surface.ROTATION_270;
// Set directly for test versus calling createCamera.
camera.processCameraProvider = MockProcessCameraProvider();
camera.camera = MockCamera();
camera.recorder = MockRecorder();
camera.videoCapture = mockVideoCapture;
camera.cameraSelector = MockCameraSelector();
// Tell plugin to mock call to get current video orientation.
camera.proxy = CameraXProxy(
getDefaultDisplayRotation: () =>
Future<int>.value(defaultTargetRotation));
const int cameraId = 87;
const String outputPath = '/temp/MOV123.temp';
// Mock method calls.
when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp'))
.thenReturn(outputPath);
when(camera.recorder!.prepareRecording(outputPath))
.thenAnswer((_) async => mockPendingRecording);
when(mockPendingRecording.start()).thenAnswer((_) async => mockRecording);
when(camera.processCameraProvider!.isBound(camera.videoCapture!))
.thenAnswer((_) async => true);
// Orientation is unlocked and plugin does not need to set default target
// rotation manually.
camera.recording = null;
await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));
verifyNever(mockVideoCapture.setTargetRotation(any));
// Orientation is locked and plugin does not need to set default target
// rotation manually.
camera.recording = null;
camera.captureOrientationLocked = true;
await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));
verifyNever(mockVideoCapture.setTargetRotation(any));
// Orientation is locked and plugin does need to set default target
// rotation manually.
camera.recording = null;
camera.captureOrientationLocked = true;
camera.shouldSetDefaultRotation = true;
await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));
verifyNever(mockVideoCapture.setTargetRotation(any));
// Orientation is unlocked and plugin does need to set default target
// rotation manually.
camera.recording = null;
camera.captureOrientationLocked = false;
camera.shouldSetDefaultRotation = true;
await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));
verify(mockVideoCapture.setTargetRotation(defaultTargetRotation));
});
test('pauseVideoRecording pauses the recording', () async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockRecording recording = MockRecording();
@ -1321,6 +1393,9 @@ void main() {
// Set directly for test versus calling createCamera.
camera.imageCapture = MockImageCapture();
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
when(camera.imageCapture!.takePicture())
.thenAnswer((_) async => testPicturePath);
@ -1329,6 +1404,51 @@ void main() {
expect(imageFile.path, equals(testPicturePath));
});
test(
'takePicture sets ImageCapture target rotation to currrent photo rotation when orientation unlocked',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
final MockImageCapture mockImageCapture = MockImageCapture();
const int cameraId = 3;
const int defaultTargetRotation = Surface.ROTATION_180;
// Set directly for test versus calling createCamera.
camera.imageCapture = mockImageCapture;
// Tell plugin to mock call to get current photo orientation.
camera.proxy = CameraXProxy(
getDefaultDisplayRotation: () =>
Future<int>.value(defaultTargetRotation));
when(camera.imageCapture!.takePicture())
.thenAnswer((_) async => 'test/absolute/path/to/picture');
// Orientation is unlocked and plugin does not need to set default target
// rotation manually.
await camera.takePicture(cameraId);
verifyNever(mockImageCapture.setTargetRotation(any));
// Orientation is locked and plugin does not need to set default target
// rotation manually.
camera.captureOrientationLocked = true;
await camera.takePicture(cameraId);
verifyNever(mockImageCapture.setTargetRotation(any));
// Orientation is locked and plugin does need to set default target
// rotation manually.
camera.captureOrientationLocked = true;
camera.shouldSetDefaultRotation = true;
await camera.takePicture(cameraId);
verifyNever(mockImageCapture.setTargetRotation(any));
// Orientation is unlocked and plugin does need to set default target
// rotation manually.
camera.captureOrientationLocked = false;
camera.shouldSetDefaultRotation = true;
await camera.takePicture(cameraId);
verify(mockImageCapture.setTargetRotation(defaultTargetRotation));
});
test('takePicture turns non-torch flash mode off when torch mode enabled',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
@ -1339,6 +1459,9 @@ void main() {
camera.imageCapture = MockImageCapture();
camera.camera = MockCamera();
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
when(camera.camera!.getCameraControl())
.thenAnswer((_) async => mockCameraControl);
@ -1358,6 +1481,9 @@ void main() {
camera.imageCapture = MockImageCapture();
camera.camera = MockCamera();
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
when(camera.camera!.getCameraControl())
.thenAnswer((_) async => mockCameraControl);
@ -1568,6 +1694,9 @@ void main() {
camera.cameraSelector = MockCameraSelector();
camera.imageAnalysis = MockImageAnalysis();
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
@ -1608,6 +1737,9 @@ void main() {
camera.cameraSelector = MockCameraSelector();
camera.imageAnalysis = MockImageAnalysis();
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
@ -1669,6 +1801,9 @@ void main() {
camera.cameraSelector = mockCameraSelector;
camera.imageAnalysis = mockImageAnalysis;
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
when(mockProcessCameraProvider.isBound(mockImageAnalysis))
.thenAnswer((_) async => Future<bool>.value(false));
when(mockProcessCameraProvider
@ -1725,6 +1860,12 @@ void main() {
// Set directly for test versus calling createCamera.
camera.imageAnalysis = mockImageAnalysis;
// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;
// Tell plugin to create a detached analyzer for testing purposes.
camera.proxy = CameraXProxy(createAnalyzer: (_) => MockAnalyzer());
final StreamSubscription<CameraImageData> imageStreamSubscription = camera
.onStreamedFrameAvailable(cameraId)
.listen((CameraImageData data) {});
@ -1733,4 +1874,117 @@ void main() {
verify(mockImageAnalysis.clearAnalyzer());
});
test(
'onStreamedFrameAvailable sets ImageAnalysis target rotation to current photo orientation when orientation unlocked',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 35;
const int defaultTargetRotation = Surface.ROTATION_90;
final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
// Set directly for test versus calling createCamera.
camera.imageAnalysis = mockImageAnalysis;
// Tell plugin to create a detached analyzer for testing purposes and mock
// call to get current photo orientation.
camera.proxy = CameraXProxy(
createAnalyzer: (_) => MockAnalyzer(),
getDefaultDisplayRotation: () =>
Future<int>.value(defaultTargetRotation));
// Orientation is unlocked and plugin does not need to set default target
// rotation manually.
StreamSubscription<CameraImageData> imageStreamSubscription = camera
.onStreamedFrameAvailable(cameraId)
.listen((CameraImageData data) {});
await untilCalled(mockImageAnalysis.setAnalyzer(any));
verifyNever(mockImageAnalysis.setTargetRotation(any));
await imageStreamSubscription.cancel();
// Orientation is locked and plugin does not need to set default target
// rotation manually.
camera.captureOrientationLocked = true;
imageStreamSubscription = camera
.onStreamedFrameAvailable(cameraId)
.listen((CameraImageData data) {});
await untilCalled(mockImageAnalysis.setAnalyzer(any));
verifyNever(mockImageAnalysis.setTargetRotation(any));
await imageStreamSubscription.cancel();
// Orientation is locked and plugin does need to set default target
// rotation manually.
camera.captureOrientationLocked = true;
camera.shouldSetDefaultRotation = true;
imageStreamSubscription = camera
.onStreamedFrameAvailable(cameraId)
.listen((CameraImageData data) {});
await untilCalled(mockImageAnalysis.setAnalyzer(any));
verifyNever(mockImageAnalysis.setTargetRotation(any));
await imageStreamSubscription.cancel();
// Orientation is unlocked and plugin does need to set default target
// rotation manually.
camera.captureOrientationLocked = false;
camera.shouldSetDefaultRotation = true;
imageStreamSubscription = camera
.onStreamedFrameAvailable(cameraId)
.listen((CameraImageData data) {});
await untilCalled(
mockImageAnalysis.setTargetRotation(defaultTargetRotation));
await imageStreamSubscription.cancel();
});
test(
'lockCaptureOrientation sets capture-related use case target rotations to correct orientation',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 44;
final MockImageAnalysis mockImageAnalysis = MockImageAnalysis();
final MockImageCapture mockImageCapture = MockImageCapture();
final MockVideoCapture mockVideoCapture = MockVideoCapture();
// Set directly for test versus calling createCamera.
camera.imageAnalysis = mockImageAnalysis;
camera.imageCapture = mockImageCapture;
camera.videoCapture = mockVideoCapture;
for (final DeviceOrientation orientation in DeviceOrientation.values) {
int? expectedTargetRotation;
switch (orientation) {
case DeviceOrientation.portraitUp:
expectedTargetRotation = Surface.ROTATION_0;
case DeviceOrientation.landscapeLeft:
expectedTargetRotation = Surface.ROTATION_90;
case DeviceOrientation.portraitDown:
expectedTargetRotation = Surface.ROTATION_180;
case DeviceOrientation.landscapeRight:
expectedTargetRotation = Surface.ROTATION_270;
}
await camera.lockCaptureOrientation(cameraId, orientation);
verify(mockImageAnalysis.setTargetRotation(expectedTargetRotation));
verify(mockImageCapture.setTargetRotation(expectedTargetRotation));
verify(mockVideoCapture.setTargetRotation(expectedTargetRotation));
expect(camera.captureOrientationLocked, isTrue);
expect(camera.shouldSetDefaultRotation, isTrue);
// Reset flags for testing.
camera.captureOrientationLocked = false;
camera.shouldSetDefaultRotation = false;
}
});
test(
'unlockCaptureOrientation sets capture-related use case target rotations to current photo/video orientation',
() async {
final AndroidCameraCameraX camera = AndroidCameraCameraX();
const int cameraId = 57;
camera.captureOrientationLocked = true;
await camera.unlockCaptureOrientation(cameraId);
expect(camera.captureOrientationLocked, isFalse);
});
}

View File

@ -516,6 +516,16 @@ class MockFallbackStrategy extends _i1.Mock implements _i21.FallbackStrategy {
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
class MockImageAnalysis extends _i1.Mock implements _i22.ImageAnalysis {
@override
_i16.Future<void> setTargetRotation(int? rotation) => (super.noSuchMethod(
Invocation.method(
#setTargetRotation,
[rotation],
),
returnValue: _i16.Future<void>.value(),
returnValueForMissingStub: _i16.Future<void>.value(),
) as _i16.Future<void>);
@override
_i16.Future<void> setAnalyzer(_i15.Analyzer? analyzer) => (super.noSuchMethod(
Invocation.method(
@ -542,6 +552,16 @@ class MockImageAnalysis extends _i1.Mock implements _i22.ImageAnalysis {
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
class MockImageCapture extends _i1.Mock implements _i23.ImageCapture {
@override
_i16.Future<void> setTargetRotation(int? rotation) => (super.noSuchMethod(
Invocation.method(
#setTargetRotation,
[rotation],
),
returnValue: _i16.Future<void>.value(),
returnValueForMissingStub: _i16.Future<void>.value(),
) as _i16.Future<void>);
@override
_i16.Future<void> setFlashMode(int? newFlashMode) => (super.noSuchMethod(
Invocation.method(
@ -708,6 +728,16 @@ class MockPlaneProxy extends _i1.Mock implements _i25.PlaneProxy {
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
class MockPreview extends _i1.Mock implements _i28.Preview {
@override
_i16.Future<void> setTargetRotation(int? rotation) => (super.noSuchMethod(
Invocation.method(
#setTargetRotation,
[rotation],
),
returnValue: _i16.Future<void>.value(),
returnValueForMissingStub: _i16.Future<void>.value(),
) as _i16.Future<void>);
@override
_i16.Future<int> setSurfaceProvider() => (super.noSuchMethod(
Invocation.method(
@ -944,6 +974,16 @@ class MockRecording extends _i1.Mock implements _i8.Recording {
/// See the documentation for Mockito's code generation for more information.
// ignore: must_be_immutable
class MockVideoCapture extends _i1.Mock implements _i34.VideoCapture {
@override
_i16.Future<void> setTargetRotation(int? rotation) => (super.noSuchMethod(
Invocation.method(
#setTargetRotation,
[rotation],
),
returnValue: _i16.Future<void>.value(),
returnValueForMissingStub: _i16.Future<void>.value(),
) as _i16.Future<void>);
@override
_i16.Future<_i11.Recorder> getOutput() => (super.noSuchMethod(
Invocation.method(
@ -1185,31 +1225,6 @@ class MockTestSystemServicesHostApi extends _i1.Mock
_i16.Future<_i7.CameraPermissionsErrorData?>.value(),
) as _i16.Future<_i7.CameraPermissionsErrorData?>);
@override
void startListeningForDeviceOrientationChange(
bool? isFrontFacing,
int? sensorOrientation,
) =>
super.noSuchMethod(
Invocation.method(
#startListeningForDeviceOrientationChange,
[
isFrontFacing,
sensorOrientation,
],
),
returnValueForMissingStub: null,
);
@override
void stopListeningForDeviceOrientationChange() => super.noSuchMethod(
Invocation.method(
#stopListeningForDeviceOrientationChange,
[],
),
returnValueForMissingStub: null,
);
@override
String getTempFilePath(
String? prefix,

View File

@ -0,0 +1,85 @@
// 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.
import 'package:camera_android_camerax/src/device_orientation_manager.dart';
import 'package:camera_android_camerax/src/surface.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart'
show DeviceOrientationChangedEvent;
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'device_orientation_manager_test.mocks.dart';
import 'test_camerax_library.g.dart';
@GenerateMocks(
<Type>[TestInstanceManagerHostApi, TestDeviceOrientationManagerHostApi])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
// Mocks the call to clear the native InstanceManager.
TestInstanceManagerHostApi.setup(MockTestInstanceManagerHostApi());
group('DeviceOrientationManager', () {
tearDown(() => TestProcessCameraProviderHostApi.setup(null));
test(
'startListeningForDeviceOrientationChange makes request to start listening for new device orientations',
() async {
final MockTestDeviceOrientationManagerHostApi mockApi =
MockTestDeviceOrientationManagerHostApi();
TestDeviceOrientationManagerHostApi.setup(mockApi);
DeviceOrientationManager.startListeningForDeviceOrientationChange(
true, 90);
verify(mockApi.startListeningForDeviceOrientationChange(true, 90));
});
test(
'stopListeningForDeviceOrientationChange makes request to stop listening for new device orientations',
() async {
final MockTestDeviceOrientationManagerHostApi mockApi =
MockTestDeviceOrientationManagerHostApi();
TestDeviceOrientationManagerHostApi.setup(mockApi);
DeviceOrientationManager.stopListeningForDeviceOrientationChange();
verify(mockApi.stopListeningForDeviceOrientationChange());
});
test('getDefaultDisplayRotation retrieves expected rotation', () async {
final MockTestDeviceOrientationManagerHostApi mockApi =
MockTestDeviceOrientationManagerHostApi();
TestDeviceOrientationManagerHostApi.setup(mockApi);
const int expectedRotation = Surface.ROTATION_180;
when(mockApi.getDefaultDisplayRotation()).thenReturn(expectedRotation);
expect(await DeviceOrientationManager.getDefaultDisplayRotation(),
equals(expectedRotation));
verify(mockApi.getDefaultDisplayRotation());
});
test('onDeviceOrientationChanged adds new orientation to stream', () {
DeviceOrientationManager.deviceOrientationChangedStreamController.stream
.listen((DeviceOrientationChangedEvent event) {
expect(event.orientation, equals(DeviceOrientation.landscapeLeft));
});
DeviceOrientationManagerFlutterApiImpl()
.onDeviceOrientationChanged('LANDSCAPE_LEFT');
});
test(
'onDeviceOrientationChanged throws error if new orientation is invalid',
() {
expect(
() => DeviceOrientationManagerFlutterApiImpl()
.onDeviceOrientationChanged('FAKE_ORIENTATION'),
throwsA(isA<ArgumentError>().having(
(ArgumentError e) => e.message,
'message',
'"FAKE_ORIENTATION" is not a valid DeviceOrientation value')));
});
});
}

View File

@ -0,0 +1,84 @@
// Mocks generated by Mockito 5.4.3 from annotations
// in camera_android_camerax/test/device_orientation_manager_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:mockito/mockito.dart' as _i1;
import 'test_camerax_library.g.dart' as _i2;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
/// A class which mocks [TestInstanceManagerHostApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockTestInstanceManagerHostApi extends _i1.Mock
implements _i2.TestInstanceManagerHostApi {
MockTestInstanceManagerHostApi() {
_i1.throwOnMissingStub(this);
}
@override
void clear() => super.noSuchMethod(
Invocation.method(
#clear,
[],
),
returnValueForMissingStub: null,
);
}
/// A class which mocks [TestDeviceOrientationManagerHostApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockTestDeviceOrientationManagerHostApi extends _i1.Mock
implements _i2.TestDeviceOrientationManagerHostApi {
MockTestDeviceOrientationManagerHostApi() {
_i1.throwOnMissingStub(this);
}
@override
void startListeningForDeviceOrientationChange(
bool? isFrontFacing,
int? sensorOrientation,
) =>
super.noSuchMethod(
Invocation.method(
#startListeningForDeviceOrientationChange,
[
isFrontFacing,
sensorOrientation,
],
),
returnValueForMissingStub: null,
);
@override
void stopListeningForDeviceOrientationChange() => super.noSuchMethod(
Invocation.method(
#stopListeningForDeviceOrientationChange,
[],
),
returnValueForMissingStub: null,
);
@override
int getDefaultDisplayRotation() => (super.noSuchMethod(
Invocation.method(
#getDefaultDisplayRotation,
[],
),
returnValue: 0,
) as int);
}

View File

@ -7,6 +7,7 @@ import 'package:camera_android_camerax/src/image_analysis.dart';
import 'package:camera_android_camerax/src/image_proxy.dart';
import 'package:camera_android_camerax/src/instance_manager.dart';
import 'package:camera_android_camerax/src/resolution_selector.dart';
import 'package:camera_android_camerax/src/surface.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@ -30,6 +31,24 @@ void main() {
TestImageAnalysisHostApi.setup(null);
});
test('detached create does not call create on the Java side', () {
final MockTestImageAnalysisHostApi mockApi =
MockTestImageAnalysisHostApi();
TestImageAnalysisHostApi.setup(mockApi);
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
ImageAnalysis.detached(
initialTargetRotation: Surface.ROTATION_270,
resolutionSelector: MockResolutionSelector(),
instanceManager: instanceManager,
);
verifyNever(mockApi.create(argThat(isA<int>()), argThat(isA<int>()),
argThat(isA<ResolutionSelector>())));
});
test('create calls create on the Java side', () {
final MockTestImageAnalysisHostApi mockApi =
MockTestImageAnalysisHostApi();
@ -39,6 +58,7 @@ void main() {
onWeakReferenceRemoved: (_) {},
);
const int targetRotation = Surface.ROTATION_90;
final MockResolutionSelector mockResolutionSelector =
MockResolutionSelector();
const int mockResolutionSelectorId = 24;
@ -50,15 +70,43 @@ void main() {
});
final ImageAnalysis instance = ImageAnalysis(
initialTargetRotation: targetRotation,
resolutionSelector: mockResolutionSelector,
instanceManager: instanceManager,
);
verify(mockApi.create(
argThat(equals(instanceManager.getIdentifier(instance))),
argThat(equals(targetRotation)),
argThat(equals(mockResolutionSelectorId))));
});
test(
'setTargetRotation makes call to set target rotation for ImageAnalysis instance',
() async {
final MockTestImageAnalysisHostApi mockApi =
MockTestImageAnalysisHostApi();
TestImageAnalysisHostApi.setup(mockApi);
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
const int targetRotation = Surface.ROTATION_180;
final ImageAnalysis imageAnalysis = ImageAnalysis.detached(
instanceManager: instanceManager,
);
instanceManager.addHostCreatedInstance(
imageAnalysis,
0,
onCopy: (_) => ImageAnalysis.detached(instanceManager: instanceManager),
);
await imageAnalysis.setTargetRotation(targetRotation);
verify(mockApi.setTargetRotation(
instanceManager.getIdentifier(imageAnalysis), targetRotation));
});
test('setAnalyzer makes call to set analyzer on ImageAnalysis instance',
() async {
final MockTestImageAnalysisHostApi mockApi =

View File

@ -33,6 +33,7 @@ class MockTestImageAnalysisHostApi extends _i1.Mock
@override
void create(
int? identifier,
int? targetRotation,
int? resolutionSelectorId,
) =>
super.noSuchMethod(
@ -40,6 +41,7 @@ class MockTestImageAnalysisHostApi extends _i1.Mock
#create,
[
identifier,
targetRotation,
resolutionSelectorId,
],
),
@ -70,6 +72,22 @@ class MockTestImageAnalysisHostApi extends _i1.Mock
),
returnValueForMissingStub: null,
);
@override
void setTargetRotation(
int? identifier,
int? rotation,
) =>
super.noSuchMethod(
Invocation.method(
#setTargetRotation,
[
identifier,
rotation,
],
),
returnValueForMissingStub: null,
);
}
/// A class which mocks [TestInstanceManagerHostApi].

View File

@ -5,6 +5,7 @@
import 'package:camera_android_camerax/src/image_capture.dart';
import 'package:camera_android_camerax/src/instance_manager.dart';
import 'package:camera_android_camerax/src/resolution_selector.dart';
import 'package:camera_android_camerax/src/surface.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@ -35,12 +36,13 @@ void main() {
);
ImageCapture.detached(
instanceManager: instanceManager,
initialTargetRotation: Surface.ROTATION_180,
targetFlashMode: ImageCapture.flashModeOn,
resolutionSelector: MockResolutionSelector(),
);
verifyNever(mockApi.create(argThat(isA<int>()), argThat(isA<int>()),
argThat(isA<ResolutionSelector>())));
argThat(isA<ResolutionSelector>()), argThat(isA<int>())));
});
test('create calls create on the Java side', () async {
@ -50,6 +52,8 @@ void main() {
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
const int targetRotation = Surface.ROTATION_270;
const int targetFlashMode = ImageCapture.flashModeAuto;
final MockResolutionSelector mockResolutionSelector =
MockResolutionSelector();
@ -63,12 +67,14 @@ void main() {
ImageCapture(
instanceManager: instanceManager,
initialTargetRotation: targetRotation,
targetFlashMode: targetFlashMode,
resolutionSelector: mockResolutionSelector,
);
verify(mockApi.create(
argThat(isA<int>()),
argThat(equals(targetRotation)),
argThat(equals(targetFlashMode)),
argThat(equals(mockResolutionSelectorId))));
});
@ -97,6 +103,31 @@ void main() {
instanceManager.getIdentifier(imageCapture), flashMode));
});
test(
'setTargetRotation makes call to set target rotation for ImageCapture instance',
() async {
final MockTestImageCaptureHostApi mockApi = MockTestImageCaptureHostApi();
TestImageCaptureHostApi.setup(mockApi);
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
const int targetRotation = Surface.ROTATION_180;
final ImageCapture imageCapture = ImageCapture.detached(
instanceManager: instanceManager,
);
instanceManager.addHostCreatedInstance(
imageCapture,
0,
onCopy: (_) => ImageCapture.detached(instanceManager: instanceManager),
);
await imageCapture.setTargetRotation(targetRotation);
verify(mockApi.setTargetRotation(
instanceManager.getIdentifier(imageCapture), targetRotation));
});
test('takePicture makes call to capture still image', () async {
final MockTestImageCaptureHostApi mockApi = MockTestImageCaptureHostApi();
TestImageCaptureHostApi.setup(mockApi);

View File

@ -36,6 +36,7 @@ class MockTestImageCaptureHostApi extends _i1.Mock
@override
void create(
int? identifier,
int? targetRotation,
int? flashMode,
int? resolutionSelectorId,
) =>
@ -44,6 +45,7 @@ class MockTestImageCaptureHostApi extends _i1.Mock
#create,
[
identifier,
targetRotation,
flashMode,
resolutionSelectorId,
],
@ -81,6 +83,22 @@ class MockTestImageCaptureHostApi extends _i1.Mock
),
)),
) as _i3.Future<String>);
@override
void setTargetRotation(
int? identifier,
int? rotation,
) =>
super.noSuchMethod(
Invocation.method(
#setTargetRotation,
[
identifier,
rotation,
],
),
returnValueForMissingStub: null,
);
}
/// A class which mocks [TestInstanceManagerHostApi].

View File

@ -6,6 +6,7 @@ import 'package:camera_android_camerax/src/camerax_library.g.dart';
import 'package:camera_android_camerax/src/instance_manager.dart';
import 'package:camera_android_camerax/src/preview.dart';
import 'package:camera_android_camerax/src/resolution_selector.dart';
import 'package:camera_android_camerax/src/surface.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@ -33,7 +34,7 @@ void main() {
);
Preview.detached(
instanceManager: instanceManager,
targetRotation: 90,
initialTargetRotation: Surface.ROTATION_90,
resolutionSelector: MockResolutionSelector(),
);
@ -48,7 +49,7 @@ void main() {
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
const int targetRotation = 90;
const int targetRotation = Surface.ROTATION_90;
final MockResolutionSelector mockResolutionSelector =
MockResolutionSelector();
const int mockResolutionSelectorId = 24;
@ -61,7 +62,7 @@ void main() {
Preview(
instanceManager: instanceManager,
targetRotation: targetRotation,
initialTargetRotation: targetRotation,
resolutionSelector: mockResolutionSelector,
);
@ -71,6 +72,31 @@ void main() {
argThat(equals(mockResolutionSelectorId))));
});
test(
'setTargetRotation makes call to set target rotation for Preview instance',
() async {
final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi();
TestPreviewHostApi.setup(mockApi);
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
const int targetRotation = Surface.ROTATION_180;
final Preview preview = Preview.detached(
instanceManager: instanceManager,
);
instanceManager.addHostCreatedInstance(
preview,
0,
onCopy: (_) => Preview.detached(instanceManager: instanceManager),
);
await preview.setTargetRotation(targetRotation);
verify(mockApi.setTargetRotation(
instanceManager.getIdentifier(preview), targetRotation));
});
test(
'setSurfaceProvider makes call to set surface provider for preview instance',
() async {

View File

@ -111,6 +111,22 @@ class MockTestPreviewHostApi extends _i1.Mock
),
),
) as _i2.ResolutionInfo);
@override
void setTargetRotation(
int? identifier,
int? rotation,
) =>
super.noSuchMethod(
Invocation.method(
#setTargetRotation,
[
identifier,
rotation,
],
),
returnValueForMissingStub: null,
);
}
/// A class which mocks [ResolutionSelector].

View File

@ -6,8 +6,7 @@ import 'package:camera_android_camerax/src/camerax_library.g.dart'
show CameraPermissionsErrorData;
import 'package:camera_android_camerax/src/system_services.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart'
show CameraException, DeviceOrientationChangedEvent;
import 'package:flutter/services.dart';
show CameraException;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@ -62,45 +61,6 @@ void main() {
verify(mockApi.requestCameraPermissions(true));
});
test('startListeningForDeviceOrientationChangeTest', () async {
final MockTestSystemServicesHostApi mockApi =
MockTestSystemServicesHostApi();
TestSystemServicesHostApi.setup(mockApi);
SystemServices.startListeningForDeviceOrientationChange(true, 90);
verify(mockApi.startListeningForDeviceOrientationChange(true, 90));
});
test('stopListeningForDeviceOrientationChangeTest', () async {
final MockTestSystemServicesHostApi mockApi =
MockTestSystemServicesHostApi();
TestSystemServicesHostApi.setup(mockApi);
SystemServices.stopListeningForDeviceOrientationChange();
verify(mockApi.stopListeningForDeviceOrientationChange());
});
test('onDeviceOrientationChanged adds new orientation to stream', () {
SystemServices.deviceOrientationChangedStreamController.stream
.listen((DeviceOrientationChangedEvent event) {
expect(event.orientation, equals(DeviceOrientation.landscapeLeft));
});
SystemServicesFlutterApiImpl()
.onDeviceOrientationChanged('LANDSCAPE_LEFT');
});
test(
'onDeviceOrientationChanged throws error if new orientation is invalid',
() {
expect(
() => SystemServicesFlutterApiImpl()
.onDeviceOrientationChanged('FAKE_ORIENTATION'),
throwsA(isA<ArgumentError>().having(
(ArgumentError e) => e.message,
'message',
'"FAKE_ORIENTATION" is not a valid DeviceOrientation value')));
});
test('onCameraError adds new error to stream', () {
const String testErrorDescription = 'Test error description!';
SystemServices.cameraErrorStreamController.stream

View File

@ -63,31 +63,6 @@ class MockTestSystemServicesHostApi extends _i1.Mock
returnValue: _i3.Future<_i4.CameraPermissionsErrorData?>.value(),
) as _i3.Future<_i4.CameraPermissionsErrorData?>);
@override
void startListeningForDeviceOrientationChange(
bool? isFrontFacing,
int? sensorOrientation,
) =>
super.noSuchMethod(
Invocation.method(
#startListeningForDeviceOrientationChange,
[
isFrontFacing,
sensorOrientation,
],
),
returnValueForMissingStub: null,
);
@override
void stopListeningForDeviceOrientationChange() => super.noSuchMethod(
Invocation.method(
#stopListeningForDeviceOrientationChange,
[],
),
returnValueForMissingStub: null,
);
@override
String getTempFilePath(
String? prefix,

View File

@ -508,11 +508,6 @@ abstract class TestSystemServicesHostApi {
Future<CameraPermissionsErrorData?> requestCameraPermissions(
bool enableAudio);
void startListeningForDeviceOrientationChange(
bool isFrontFacing, int sensorOrientation);
void stopListeningForDeviceOrientationChange();
String getTempFilePath(String prefix, String suffix);
static void setup(TestSystemServicesHostApi? api,
@ -541,51 +536,6 @@ abstract class TestSystemServicesHostApi {
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final bool? arg_isFrontFacing = (args[0] as bool?);
assert(arg_isFrontFacing != null,
'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null, expected non-null bool.');
final int? arg_sensorOrientation = (args[1] as int?);
assert(arg_sensorOrientation != null,
'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null, expected non-null int.');
api.startListeningForDeviceOrientationChange(
arg_isFrontFacing!, arg_sensorOrientation!);
return <Object?>[];
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
// ignore message
api.stopListeningForDeviceOrientationChange();
return <Object?>[];
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.SystemServicesHostApi.getTempFilePath', codec,
@ -614,6 +564,86 @@ abstract class TestSystemServicesHostApi {
}
}
abstract class TestDeviceOrientationManagerHostApi {
static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding =>
TestDefaultBinaryMessengerBinding.instance;
static const MessageCodec<Object?> codec = StandardMessageCodec();
void startListeningForDeviceOrientationChange(
bool isFrontFacing, int sensorOrientation);
void stopListeningForDeviceOrientationChange();
int getDefaultDisplayRotation();
static void setup(TestDeviceOrientationManagerHostApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.DeviceOrientationManagerHostApi.startListeningForDeviceOrientationChange',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.DeviceOrientationManagerHostApi.startListeningForDeviceOrientationChange was null.');
final List<Object?> args = (message as List<Object?>?)!;
final bool? arg_isFrontFacing = (args[0] as bool?);
assert(arg_isFrontFacing != null,
'Argument for dev.flutter.pigeon.DeviceOrientationManagerHostApi.startListeningForDeviceOrientationChange was null, expected non-null bool.');
final int? arg_sensorOrientation = (args[1] as int?);
assert(arg_sensorOrientation != null,
'Argument for dev.flutter.pigeon.DeviceOrientationManagerHostApi.startListeningForDeviceOrientationChange was null, expected non-null int.');
api.startListeningForDeviceOrientationChange(
arg_isFrontFacing!, arg_sensorOrientation!);
return <Object?>[];
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.DeviceOrientationManagerHostApi.stopListeningForDeviceOrientationChange',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
// ignore message
api.stopListeningForDeviceOrientationChange();
return <Object?>[];
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.DeviceOrientationManagerHostApi.getDefaultDisplayRotation',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
// ignore message
final int output = api.getDefaultDisplayRotation();
return <Object?>[output];
});
}
}
}
}
class _TestPreviewHostApiCodec extends StandardMessageCodec {
const _TestPreviewHostApiCodec();
@override
@ -650,6 +680,8 @@ abstract class TestPreviewHostApi {
ResolutionInfo getResolutionInfo(int identifier);
void setTargetRotation(int identifier, int rotation);
static void setup(TestPreviewHostApi? api,
{BinaryMessenger? binaryMessenger}) {
{
@ -738,6 +770,31 @@ abstract class TestPreviewHostApi {
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.PreviewHostApi.setTargetRotation', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.PreviewHostApi.setTargetRotation was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.PreviewHostApi.setTargetRotation was null, expected non-null int.');
final int? arg_rotation = (args[1] as int?);
assert(arg_rotation != null,
'Argument for dev.flutter.pigeon.PreviewHostApi.setTargetRotation was null, expected non-null int.');
api.setTargetRotation(arg_identifier!, arg_rotation!);
return <Object?>[];
});
}
}
}
}
@ -750,6 +807,8 @@ abstract class TestVideoCaptureHostApi {
int getOutput(int identifier);
void setTargetRotation(int identifier, int rotation);
static void setup(TestVideoCaptureHostApi? api,
{BinaryMessenger? binaryMessenger}) {
{
@ -796,6 +855,31 @@ abstract class TestVideoCaptureHostApi {
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.VideoCaptureHostApi.setTargetRotation', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.VideoCaptureHostApi.setTargetRotation was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.VideoCaptureHostApi.setTargetRotation was null, expected non-null int.');
final int? arg_rotation = (args[1] as int?);
assert(arg_rotation != null,
'Argument for dev.flutter.pigeon.VideoCaptureHostApi.setTargetRotation was null, expected non-null int.');
api.setTargetRotation(arg_identifier!, arg_rotation!);
return <Object?>[];
});
}
}
}
}
@ -1059,12 +1143,15 @@ abstract class TestImageCaptureHostApi {
TestDefaultBinaryMessengerBinding.instance;
static const MessageCodec<Object?> codec = StandardMessageCodec();
void create(int identifier, int? flashMode, int? resolutionSelectorId);
void create(int identifier, int? targetRotation, int? flashMode,
int? resolutionSelectorId);
void setFlashMode(int identifier, int flashMode);
Future<String> takePicture(int identifier);
void setTargetRotation(int identifier, int rotation);
static void setup(TestImageCaptureHostApi? api,
{BinaryMessenger? binaryMessenger}) {
{
@ -1084,9 +1171,11 @@ abstract class TestImageCaptureHostApi {
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.ImageCaptureHostApi.create was null, expected non-null int.');
final int? arg_flashMode = (args[1] as int?);
final int? arg_resolutionSelectorId = (args[2] as int?);
api.create(arg_identifier!, arg_flashMode, arg_resolutionSelectorId);
final int? arg_targetRotation = (args[1] as int?);
final int? arg_flashMode = (args[2] as int?);
final int? arg_resolutionSelectorId = (args[3] as int?);
api.create(arg_identifier!, arg_targetRotation, arg_flashMode,
arg_resolutionSelectorId);
return <Object?>[];
});
}
@ -1138,6 +1227,31 @@ abstract class TestImageCaptureHostApi {
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.ImageCaptureHostApi.setTargetRotation', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.ImageCaptureHostApi.setTargetRotation was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.ImageCaptureHostApi.setTargetRotation was null, expected non-null int.');
final int? arg_rotation = (args[1] as int?);
assert(arg_rotation != null,
'Argument for dev.flutter.pigeon.ImageCaptureHostApi.setTargetRotation was null, expected non-null int.');
api.setTargetRotation(arg_identifier!, arg_rotation!);
return <Object?>[];
});
}
}
}
}
@ -1285,12 +1399,14 @@ abstract class TestImageAnalysisHostApi {
TestDefaultBinaryMessengerBinding.instance;
static const MessageCodec<Object?> codec = StandardMessageCodec();
void create(int identifier, int? resolutionSelectorId);
void create(int identifier, int? targetRotation, int? resolutionSelectorId);
void setAnalyzer(int identifier, int analyzerIdentifier);
void clearAnalyzer(int identifier);
void setTargetRotation(int identifier, int rotation);
static void setup(TestImageAnalysisHostApi? api,
{BinaryMessenger? binaryMessenger}) {
{
@ -1310,8 +1426,10 @@ abstract class TestImageAnalysisHostApi {
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.ImageAnalysisHostApi.create was null, expected non-null int.');
final int? arg_resolutionSelectorId = (args[1] as int?);
api.create(arg_identifier!, arg_resolutionSelectorId);
final int? arg_targetRotation = (args[1] as int?);
final int? arg_resolutionSelectorId = (args[2] as int?);
api.create(
arg_identifier!, arg_targetRotation, arg_resolutionSelectorId);
return <Object?>[];
});
}
@ -1363,6 +1481,31 @@ abstract class TestImageAnalysisHostApi {
});
}
}
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.ImageAnalysisHostApi.setTargetRotation', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel, null);
} else {
_testBinaryMessengerBinding!.defaultBinaryMessenger
.setMockDecodedMessageHandler<Object?>(channel,
(Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.ImageAnalysisHostApi.setTargetRotation was null.');
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_identifier = (args[0] as int?);
assert(arg_identifier != null,
'Argument for dev.flutter.pigeon.ImageAnalysisHostApi.setTargetRotation was null, expected non-null int.');
final int? arg_rotation = (args[1] as int?);
assert(arg_rotation != null,
'Argument for dev.flutter.pigeon.ImageAnalysisHostApi.setTargetRotation was null, expected non-null int.');
api.setTargetRotation(arg_identifier!, arg_rotation!);
return <Object?>[];
});
}
}
}
}

View File

@ -5,6 +5,7 @@
import 'package:camera_android_camerax/src/camerax_library.g.dart';
import 'package:camera_android_camerax/src/instance_manager.dart';
import 'package:camera_android_camerax/src/recorder.dart';
import 'package:camera_android_camerax/src/surface.dart';
import 'package:camera_android_camerax/src/video_capture.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
@ -50,6 +51,31 @@ void main() {
verify(mockApi.withOutput(mockRecorderId));
});
test(
'setTargetRotation makes call to set target rotation for VideoCapture instance',
() async {
final MockTestVideoCaptureHostApi mockApi = MockTestVideoCaptureHostApi();
TestVideoCaptureHostApi.setup(mockApi);
final InstanceManager instanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
const int targetRotation = Surface.ROTATION_180;
final VideoCapture videoCapture = VideoCapture.detached(
instanceManager: instanceManager,
);
instanceManager.addHostCreatedInstance(
videoCapture,
0,
onCopy: (_) => VideoCapture.detached(instanceManager: instanceManager),
);
await videoCapture.setTargetRotation(targetRotation);
verify(mockApi.setTargetRotation(
instanceManager.getIdentifier(videoCapture), targetRotation));
});
test('getOutput calls the Java side and returns correct Recorder', () async {
final MockTestVideoCaptureHostApi mockApi = MockTestVideoCaptureHostApi();
TestVideoCaptureHostApi.setup(mockApi);

View File

@ -61,6 +61,22 @@ class MockTestVideoCaptureHostApi extends _i1.Mock
),
returnValue: 0,
) as int);
@override
void setTargetRotation(
int? identifier,
int? rotation,
) =>
super.noSuchMethod(
Invocation.method(
#setTargetRotation,
[
identifier,
rotation,
],
),
returnValueForMissingStub: null,
);
}
/// A class which mocks [TestInstanceManagerHostApi].