diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md
index e7d7bfd4ea4b..af2488eb6695 100644
--- a/packages/camera/camera_android_camerax/CHANGELOG.md
+++ b/packages/camera/camera_android_camerax/CHANGELOG.md
@@ -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.
diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md
index 073daa16d0eb..3a2e49d7e60d 100644
--- a/packages/camera/camera_android_camerax/README.md
+++ b/packages/camera/camera_android_camerax/README.md
@@ -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.
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java
index eeb3c02dd153..c8046db5af6d 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java
@@ -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 void setUp(
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 void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBi
systemServicesHostApiImpl.setActivity(activity);
systemServicesHostApiImpl.setPermissionsRegistry(
activityPluginBinding::addRequestPermissionsResultListener);
+ deviceOrientationManagerHostApiImpl.setActivity(activity);
}
@Override
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java
index 67cac560db4d..b5281179d728 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java
@@ -14,7 +14,6 @@
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 void stop() {
broadcastReceiver = null;
}
- /**
- * Returns the device's photo orientation in degrees based on the sensor orientation and the last
- * known UI orientation.
- *
- *
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.
- *
- *
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.
- *
- *
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.
- *
- *
Returns one of 0, 90, 180 or 270.
- *
- *
More details can be found in the official Android documentation:
- * https://developer.android.com/reference/android/media/MediaRecorder#setOrientationHint(int)
- *
- *
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 @@ static void handleOrientationChange(
@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 @@ PlatformChannel.DeviceOrientation getUIOrientation() {
}
/**
- * Calculates the sensor orientation based on the supplied angle.
- *
- *
This method is visible for testing purposes only and should never be used outside this
- * class.
- *
- * @param angle Orientation angle.
- * @return The sensor orientation based on the supplied angle.
- */
- @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.
+ * Gets default capture rotation for CameraX {@code UseCase}s.
*
- *
This method is visible for testing purposes only and should never be used outside this
- * class.
+ *
See
+ * https://developer.android.com/reference/androidx/camera/core/ImageCapture#setTargetRotation(int),
+ * for instance.
*
- * @return The default orientation of the device.
+ * @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
- 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();
}
/**
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerFlutterApiImpl.java
new file mode 100644
index 000000000000..e3e514cfd780
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerFlutterApiImpl.java
@@ -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 reply) {
+ super.onDeviceOrientationChanged(orientation, reply);
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerHostApiImpl.java
new file mode 100644
index 000000000000..e617d53c99cd
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerHostApiImpl.java
@@ -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}.
+ *
+ * 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.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
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);
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
index 603af1f42a02..e8da19753364 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java
@@ -1256,11 +1256,6 @@ public interface SystemServicesHostApi {
void requestCameraPermissions(
@NonNull Boolean enableAudio, @NonNull Result result);
- void startListeningForDeviceOrientationChange(
- @NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation);
-
- void stopListeningForDeviceOrientationChange();
-
@NonNull
String getTempFilePath(@NonNull String prefix, @NonNull String suffix);
@@ -1309,7 +1304,85 @@ public void error(Throwable error) {
BasicMessageChannel channel =
new BasicMessageChannel<>(
binaryMessenger,
- "dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange",
+ "dev.flutter.pigeon.SystemServicesHostApi.getTempFilePath",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) message;
+ String prefixArg = (String) args.get(0);
+ String suffixArg = (String) args.get(1);
+ try {
+ String output = api.getTempFilePath(prefixArg, suffixArg);
+ wrapped.add(0, output);
+ } catch (Throwable exception) {
+ ArrayList 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 SystemServicesFlutterApi {
+ private final @NonNull BinaryMessenger binaryMessenger;
+
+ public SystemServicesFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
+ this.binaryMessenger = argBinaryMessenger;
+ }
+
+ /** Public interface for sending reply. */
+ @SuppressWarnings("UnknownNullness")
+ public interface Reply {
+ void reply(T reply);
+ }
+ /** The codec used by SystemServicesFlutterApi. */
+ static @NonNull MessageCodec getCodec() {
+ return new StandardMessageCodec();
+ }
+
+ public void onCameraError(@NonNull String errorDescriptionArg, @NonNull Reply callback) {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError",
+ getCodec());
+ channel.send(
+ new ArrayList(Collections.singletonList(errorDescriptionArg)),
+ 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 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 channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.DeviceOrientationManagerHostApi.startListeningForDeviceOrientationChange",
getCodec());
if (api != null) {
channel.setMessageHandler(
@@ -1337,7 +1410,7 @@ public void error(Throwable error) {
BasicMessageChannel channel =
new BasicMessageChannel<>(
binaryMessenger,
- "dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange",
+ "dev.flutter.pigeon.DeviceOrientationManagerHostApi.stopListeningForDeviceOrientationChange",
getCodec());
if (api != null) {
channel.setMessageHandler(
@@ -1360,17 +1433,14 @@ public void error(Throwable error) {
BasicMessageChannel channel =
new BasicMessageChannel<>(
binaryMessenger,
- "dev.flutter.pigeon.SystemServicesHostApi.getTempFilePath",
+ "dev.flutter.pigeon.DeviceOrientationManagerHostApi.getDefaultDisplayRotation",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList wrapped = new ArrayList();
- ArrayList args = (ArrayList) message;
- String prefixArg = (String) args.get(0);
- String suffixArg = (String) args.get(1);
try {
- String output = api.getTempFilePath(prefixArg, suffixArg);
+ Long output = api.getDefaultDisplayRotation();
wrapped.add(0, output);
} catch (Throwable exception) {
ArrayList wrappedError = wrapError(exception);
@@ -1385,10 +1455,10 @@ public void error(Throwable error) {
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
- public static class SystemServicesFlutterApi {
+ public static class DeviceOrientationManagerFlutterApi {
private final @NonNull BinaryMessenger binaryMessenger;
- public SystemServicesFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
+ public DeviceOrientationManagerFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
this.binaryMessenger = argBinaryMessenger;
}
@@ -1397,7 +1467,7 @@ public SystemServicesFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
public interface Reply {
void reply(T reply);
}
- /** The codec used by SystemServicesFlutterApi. */
+ /** The codec used by DeviceOrientationManagerFlutterApi. */
static @NonNull MessageCodec getCodec() {
return new StandardMessageCodec();
}
@@ -1407,23 +1477,12 @@ public void onDeviceOrientationChanged(
BasicMessageChannel channel =
new BasicMessageChannel<>(
binaryMessenger,
- "dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged",
+ "dev.flutter.pigeon.DeviceOrientationManagerFlutterApi.onDeviceOrientationChanged",
getCodec());
channel.send(
new ArrayList(Collections.singletonList(orientationArg)),
channelReply -> callback.reply(null));
}
-
- public void onCameraError(@NonNull String errorDescriptionArg, @NonNull Reply callback) {
- BasicMessageChannel channel =
- new BasicMessageChannel<>(
- binaryMessenger,
- "dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError",
- getCodec());
- channel.send(
- new ArrayList(Collections.singletonList(errorDescriptionArg)),
- channelReply -> callback.reply(null));
- }
}
private static class PreviewHostApiCodec extends StandardMessageCodec {
@@ -1466,6 +1525,8 @@ void create(
@NonNull
ResolutionInfo getResolutionInfo(@NonNull Long identifier);
+ void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation);
+
/** The codec used by PreviewHostApi. */
static @NonNull MessageCodec getCodec() {
return PreviewHostApiCodec.INSTANCE;
@@ -1577,6 +1638,32 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable PreviewHos
channel.setMessageHandler(null);
}
}
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger, "dev.flutter.pigeon.PreviewHostApi.setTargetRotation", getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) 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 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 interface VideoCaptureHostApi {
@NonNull
Long getOutput(@NonNull Long identifier);
+ void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation);
+
/** The codec used by VideoCaptureHostApi. */
static @NonNull MessageCodec getCodec() {
return new StandardMessageCodec();
@@ -1646,6 +1735,34 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable VideoCaptu
channel.setMessageHandler(null);
}
}
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.VideoCaptureHostApi.setTargetRotation",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) 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 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 void create(@NonNull Long identifierArg, @NonNull Reply callback) {
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 result);
+ void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation);
+
/** The codec used by ImageCaptureHostApi. */
static @NonNull MessageCodec getCodec() {
return new StandardMessageCodec();
@@ -2080,11 +2202,13 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable ImageCaptu
ArrayList wrapped = new ArrayList();
ArrayList args = (ArrayList) 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 void error(Throwable error) {
channel.setMessageHandler(null);
}
}
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.ImageCaptureHostApi.setTargetRotation",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) 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 wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
}
}
@@ -2486,12 +2638,17 @@ public void create(
/** 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 getCodec() {
return new StandardMessageCodec();
@@ -2512,10 +2669,12 @@ static void setup(
ArrayList wrapped = new ArrayList();
ArrayList args = (ArrayList) 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 @@ static void setup(
channel.setMessageHandler(null);
}
}
+ {
+ BasicMessageChannel channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.ImageAnalysisHostApi.setTargetRotation",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList wrapped = new ArrayList();
+ ArrayList args = (ArrayList) 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 wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java
index 00bceb76a7e5..f44db11cba1f 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisHostApiImpl.java
@@ -41,9 +41,13 @@ public void setContext(@NonNull Context context) {
/** 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 void clearAnalyzer(@NonNull Long identifier) {
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}.
*/
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java
index 88ec2debb7a8..e17d386632f5 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java
@@ -53,9 +53,15 @@ public void setContext(@NonNull Context context) {
*/
@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 void create(
/** 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 void setFlashMode(@NonNull Long identifier, @NonNull Long flashMode) {
@Override
public void takePicture(
@NonNull Long identifier, @NonNull GeneratedCameraXLibrary.Result 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 void onError(@NonNull ImageCaptureException exception) {
}
};
}
+
+ /** 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));
+ }
}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingHostApiImpl.java
index 70119fac5d41..d4acebefbffb 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingHostApiImpl.java
@@ -75,7 +75,14 @@ public void handleVideoRecordEvent(@NonNull VideoRecordEvent event) {
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 -> {});
}
}
}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java
index 07b581ebf96a..7b1ba1214cdb 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java
@@ -61,7 +61,7 @@ public void create(
*/
@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 void releaseFlutterSurfaceTexture() {
@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 void releaseFlutterSurfaceTexture() {
.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));
+ }
}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java
index 63158974f43a..2cb4ea1b2275 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java
@@ -13,11 +13,6 @@ public SystemServicesFlutterApiImpl(@NonNull BinaryMessenger binaryMessenger) {
super(binaryMessenger);
}
- public void sendDeviceOrientationChangedEvent(
- @NonNull String orientation, @NonNull Reply reply) {
- super.onDeviceOrientationChanged(orientation, reply);
- }
-
public void sendCameraError(@NonNull String errorDescription, @NonNull Reply reply) {
super.onCameraError(errorDescription, reply);
}
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java
index b8f4d6b0c62d..4e0b18069ffb 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java
@@ -7,14 +7,11 @@
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 void requestCameraPermissions(
});
}
- /**
- * Starts listening for device orientation changes using an instance of a {@link
- * DeviceOrientationManager}.
- *
- * 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.
- *
- *
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
diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/VideoCaptureHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/VideoCaptureHostApiImpl.java
index 7e764cdff4a9..1c849ee09379 100644
--- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/VideoCaptureHostApiImpl.java
+++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/VideoCaptureHostApiImpl.java
@@ -37,8 +37,7 @@ public Long withOutput(@NonNull Long videoOutputId) {
@Override
@NonNull
public Long getOutput(@NonNull Long identifier) {
- VideoCapture videoCapture =
- Objects.requireNonNull(instanceManager.getInstance(identifier));
+ VideoCapture videoCapture = getVideoCaptureInstance(identifier);
Recorder recorder = videoCapture.getOutput();
return Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(recorder));
}
@@ -49,4 +48,18 @@ public VideoCaptureFlutterApiImpl getVideoCaptureFlutterApiImpl(
@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 videoCapture = getVideoCaptureInstance(identifier);
+ videoCapture.setTargetRotation(rotation.intValue());
+ }
+
+ /**
+ * Retrieves the {@link VideoCapture} instance associated with the specified {@code identifier}.
+ */
+ private VideoCapture getVideoCaptureInstance(@NonNull Long identifier) {
+ return Objects.requireNonNull(instanceManager.getInstance(identifier));
+ }
}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java
index 4a2197624b65..58f517edc653 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java
@@ -44,6 +44,7 @@ public void onAttachedToActivity_setsLifecycleOwnerAsActivityIfLifecycleOwner()
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 void onAttachedToActivity_setsLifecycleOwnerAsActivityIfLifecycleOwner()
plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl;
plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl;
plugin.systemServicesHostApiImpl = mock(SystemServicesHostApiImpl.class);
+ plugin.deviceOrientationManagerHostApiImpl = mock(DeviceOrientationManagerHostApiImpl.class);
plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onAttachedToActivity(activityPluginBinding);
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java
index 1e2bfba714c7..1bb4077fb6dc 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java
@@ -50,108 +50,6 @@ public void before() {
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 mockedSystem = mockStatic(Settings.System.class)) {
@@ -239,60 +137,6 @@ public void getUIOrientation() {
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 @@ private void setUpUIOrientationMocks(int orientation, int rotation) {
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();
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerWrapperTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerWrapperTest.java
new file mode 100644
index 000000000000..26cfd77f1265
--- /dev/null
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerWrapperTest.java
@@ -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 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.>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));
+ }
+}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java
index 08a9f80b6373..5e4a729dbd23 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java
@@ -12,6 +12,7 @@
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 void hostApiCreate_createsExpectedImageAnalysisInstanceWithExpectedIdenti
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 void hostApiCreate_createsExpectedImageAnalysisInstanceWithExpectedIdenti
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 void clearAnalyzer_makesCallToClearAnalyzerOnExpectedImageAnalysisInstanc
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);
+ }
}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java
index df6c8ee74b7f..591037fd97cb 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java
@@ -14,6 +14,7 @@
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 void create_createsImageCaptureWithCorrectConfiguration() {
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 void takePicture_usesExpectedOnImageSavedCallback() {
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);
+ }
}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java
index 37a7c7704135..81b455d7a867 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java
@@ -217,4 +217,18 @@ public void getResolutionInfo_makesCallToRetrievePreviewResolutionInfo() {
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);
+ }
}
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java
index f905704cbc10..3636629e75f7 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java
@@ -6,7 +6,6 @@
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 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 void requestCameraPermissionsTest() {
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 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.>any());
-
- // Test that the DeviceOrientationManager starts listening for device orientation changes.
- verify(mockDeviceOrientationManager).start();
- }
-
@Test
public void getTempFilePath_returnsCorrectPath() {
final SystemServicesHostApiImpl systemServicesHostApi =
diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/VideoCaptureTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/VideoCaptureTest.java
index 95794334c134..32627fbd6245 100644
--- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/VideoCaptureTest.java
+++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/VideoCaptureTest.java
@@ -10,6 +10,7 @@
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 void withOutput_returnsNewVideoCaptureWithAssociatedRecorder() {
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 =
diff --git a/packages/camera/camera_android_camerax/example/lib/main.dart b/packages/camera/camera_android_camerax/example/lib/main.dart
index 449899aef7ed..960d50723461 100644
--- a/packages/camera/camera_android_camerax/example/lib/main.dart
+++ b/packages/camera/camera_android_camerax/example/lib/main.dart
@@ -296,14 +296,16 @@ class _CameraExampleHomeState extends State
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,
),
],
),
diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
index b7f8f8a091d2..fbb42d3664ed 100644
--- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
+++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
@@ -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> 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();
}
+ /// Locks the capture orientation.
+ @override
+ Future 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 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 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 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!, [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 onStreamedFrameAvailable(int cameraId,
{CameraImageStreamOptions? options}) {
cameraImageDataStreamController = StreamController(
- 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 _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 weakThis =
WeakReference(this);
Future 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 _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:
+ /// 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;
+ case DeviceOrientation.landscapeLeft:
return Surface.ROTATION_90;
- case 180:
+ case DeviceOrientation.portraitDown:
return Surface.ROTATION_180;
- case 270:
+ case DeviceOrientation.landscapeRight:
return Surface.ROTATION_270;
- case 0:
- return Surface.ROTATION_0;
- default:
- throw ArgumentError(
- '"$sensorOrientation" is not a valid sensor orientation value');
}
}
diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart
index b59e0df30da1..24f159a1eab5 100644
--- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart
+++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart
@@ -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);
diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
index 4dd5253bd2d6..f1528bbeec87 100644
--- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart
@@ -894,10 +894,77 @@ class SystemServicesHostApi {
}
}
+ Future getTempFilePath(String arg_prefix, String arg_suffix) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.SystemServicesHostApi.getTempFilePath', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList =
+ await channel.send([arg_prefix, arg_suffix]) as List?;
+ 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 String?)!;
+ }
+ }
+}
+
+abstract class SystemServicesFlutterApi {
+ static const MessageCodec codec = StandardMessageCodec();
+
+ void onCameraError(String errorDescription);
+
+ static void setup(SystemServicesFlutterApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError was null.');
+ final List args = (message as List?)!;
+ final String? arg_errorDescription = (args[0] as String?);
+ assert(arg_errorDescription != null,
+ 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError was null, expected non-null String.');
+ api.onCameraError(arg_errorDescription!);
+ return;
+ });
+ }
+ }
+ }
+}
+
+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 codec = StandardMessageCodec();
+
Future startListeningForDeviceOrientationChange(
bool arg_isFrontFacing, int arg_sensorOrientation) async {
final BasicMessageChannel channel = BasicMessageChannel(
- 'dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange',
+ 'dev.flutter.pigeon.DeviceOrientationManagerHostApi.startListeningForDeviceOrientationChange',
codec,
binaryMessenger: _binaryMessenger);
final List? replyList =
@@ -921,7 +988,7 @@ class SystemServicesHostApi {
Future stopListeningForDeviceOrientationChange() async {
final BasicMessageChannel channel = BasicMessageChannel(
- 'dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange',
+ 'dev.flutter.pigeon.DeviceOrientationManagerHostApi.stopListeningForDeviceOrientationChange',
codec,
binaryMessenger: _binaryMessenger);
final List? replyList = await channel.send(null) as List?;
@@ -941,12 +1008,12 @@ class SystemServicesHostApi {
}
}
- Future getTempFilePath(String arg_prefix, String arg_suffix) async {
+ Future getDefaultDisplayRotation() async {
final BasicMessageChannel channel = BasicMessageChannel(
- 'dev.flutter.pigeon.SystemServicesHostApi.getTempFilePath', codec,
+ 'dev.flutter.pigeon.DeviceOrientationManagerHostApi.getDefaultDisplayRotation',
+ codec,
binaryMessenger: _binaryMessenger);
- final List? replyList =
- await channel.send([arg_prefix, arg_suffix]) as List?;
+ final List? replyList = await channel.send(null) as List?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
@@ -964,23 +1031,21 @@ class SystemServicesHostApi {
message: 'Host platform returned null value for non-null return value.',
);
} else {
- return (replyList[0] as String?)!;
+ return (replyList[0] as int?)!;
}
}
}
-abstract class SystemServicesFlutterApi {
+abstract class DeviceOrientationManagerFlutterApi {
static const MessageCodec codec = StandardMessageCodec();
void onDeviceOrientationChanged(String orientation);
- void onCameraError(String errorDescription);
-
- static void setup(SystemServicesFlutterApi? api,
+ static void setup(DeviceOrientationManagerFlutterApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel channel = BasicMessageChannel(
- 'dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged',
+ 'dev.flutter.pigeon.DeviceOrientationManagerFlutterApi.onDeviceOrientationChanged',
codec,
binaryMessenger: binaryMessenger);
if (api == null) {
@@ -988,35 +1053,16 @@ abstract class SystemServicesFlutterApi {
} else {
channel.setMessageHandler((Object? message) async {
assert(message != null,
- 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged was null.');
+ 'Argument for dev.flutter.pigeon.DeviceOrientationManagerFlutterApi.onDeviceOrientationChanged was null.');
final List args = (message as List?)!;
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.');
+ 'Argument for dev.flutter.pigeon.DeviceOrientationManagerFlutterApi.onDeviceOrientationChanged was null, expected non-null String.');
api.onDeviceOrientationChanged(arg_orientation!);
return;
});
}
}
- {
- final BasicMessageChannel channel = BasicMessageChannel(
- 'dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError', codec,
- binaryMessenger: binaryMessenger);
- if (api == null) {
- channel.setMessageHandler(null);
- } else {
- channel.setMessageHandler((Object? message) async {
- assert(message != null,
- 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError was null.');
- final List args = (message as List?)!;
- final String? arg_errorDescription = (args[0] as String?);
- assert(arg_errorDescription != null,
- 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError was null, expected non-null String.');
- api.onCameraError(arg_errorDescription!);
- return;
- });
- }
- }
}
}
@@ -1151,6 +1197,28 @@ class PreviewHostApi {
return (replyList[0] as ResolutionInfo?)!;
}
}
+
+ Future setTargetRotation(int arg_identifier, int arg_rotation) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.PreviewHostApi.setTargetRotation', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList = await channel
+ .send([arg_identifier, arg_rotation]) as List?;
+ 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 setTargetRotation(int arg_identifier, int arg_rotation) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.VideoCaptureHostApi.setTargetRotation', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList = await channel
+ .send([arg_identifier, arg_rotation]) as List?;
+ 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 codec = StandardMessageCodec();
- Future create(int arg_identifier, int? arg_flashMode,
- int? arg_resolutionSelectorId) async {
+ Future create(int arg_identifier, int? arg_targetRotation,
+ int? arg_flashMode, int? arg_resolutionSelectorId) async {
final BasicMessageChannel channel = BasicMessageChannel(
'dev.flutter.pigeon.ImageCaptureHostApi.create', codec,
binaryMessenger: _binaryMessenger);
- final List? replyList = await channel.send(
- [arg_identifier, arg_flashMode, arg_resolutionSelectorId])
- as List?;
+ final List? replyList = await channel.send([
+ arg_identifier,
+ arg_targetRotation,
+ arg_flashMode,
+ arg_resolutionSelectorId
+ ]) as List?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
@@ -1675,6 +1768,28 @@ class ImageCaptureHostApi {
return (replyList[0] as String?)!;
}
}
+
+ Future setTargetRotation(int arg_identifier, int arg_rotation) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.ImageCaptureHostApi.setTargetRotation', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList = await channel
+ .send([arg_identifier, arg_rotation]) as List?;
+ 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 codec = StandardMessageCodec();
- Future create(int arg_identifier, int? arg_resolutionSelectorId) async {
+ Future create(int arg_identifier, int? arg_targetRotation,
+ int? arg_resolutionSelectorId) async {
final BasicMessageChannel channel = BasicMessageChannel(
'dev.flutter.pigeon.ImageAnalysisHostApi.create', codec,
binaryMessenger: _binaryMessenger);
- final List? replyList =
- await channel.send([arg_identifier, arg_resolutionSelectorId])
- as List?;
+ final List? replyList = await channel.send([
+ arg_identifier,
+ arg_targetRotation,
+ arg_resolutionSelectorId
+ ]) as List?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
@@ -2042,6 +2160,28 @@ class ImageAnalysisHostApi {
return;
}
}
+
+ Future setTargetRotation(int arg_identifier, int arg_rotation) async {
+ final BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.ImageAnalysisHostApi.setTargetRotation', codec,
+ binaryMessenger: _binaryMessenger);
+ final List? replyList = await channel
+ .send([arg_identifier, arg_rotation]) as List?;
+ 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 {
diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart
index 133d84544d9b..87d3680ff2c3 100644
--- a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart
+++ b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart
@@ -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 Function(Preview preview) setPreviewSurfaceProvider;
+ /// Returns default rotation for [UseCase]s in terms of one of the [Surface]
+ /// rotation constants.
+ Future Function() getDefaultDisplayRotation;
+
static Future _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 _setPreviewSurfaceProvider(Preview preview) async {
return preview.setSurfaceProvider();
}
+
+ static Future _getDefaultDisplayRotation() async {
+ return DeviceOrientationManager.getDefaultDisplayRotation();
+ }
}
diff --git a/packages/camera/camera_android_camerax/lib/src/device_orientation_manager.dart b/packages/camera/camera_android_camerax/lib/src/device_orientation_manager.dart
new file mode 100644
index 000000000000..10f20232485b
--- /dev/null
+++ b/packages/camera/camera_android_camerax/lib/src/device_orientation_manager.dart
@@ -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
+ deviceOrientationChangedStreamController =
+ StreamController.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 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');
+ }
+ }
+}
diff --git a/packages/camera/camera_android_camerax/lib/src/image_analysis.dart b/packages/camera/camera_android_camerax/lib/src/image_analysis.dart
index e28457c6d192..3dc21ce2a7bc 100644
--- a/packages/camera/camera_android_camerax/lib/src/image_analysis.dart
+++ b/packages/camera/camera_android_camerax/lib/src/image_analysis.dart
@@ -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 setTargetRotation(int rotation) =>
+ _api.setTargetRotationFromInstances(this, rotation);
+
/// Sets an [Analyzer] to receive and analyze images.
Future setAnalyzer(Analyzer analyzer) =>
- _api.setAnalyzerfromInstances(this, analyzer);
+ _api.setAnalyzerFromInstances(this, analyzer);
/// Removes a previously set [Analyzer].
- Future clearAnalyzer() => _api.clearAnalyzerfromInstances(this);
+ Future 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 createfromInstances(
+ Future 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 setTargetRotationFromInstances(
+ ImageAnalysis instance, int rotation) {
+ return setTargetRotation(
+ instanceManager.getIdentifier(instance)!, rotation);
+ }
+
/// Sets the [analyzer] to receive and analyze images on the [instance].
- Future setAnalyzerfromInstances(
+ Future setAnalyzerFromInstances(
ImageAnalysis instance,
Analyzer analyzer,
) {
@@ -116,7 +145,7 @@ class _ImageAnalysisHostApiImpl extends ImageAnalysisHostApi {
}
/// Removes a previously set analyzer from the [instance].
- Future clearAnalyzerfromInstances(
+ Future clearAnalyzerFromInstances(
ImageAnalysis instance,
) {
return clearAnalyzer(
diff --git a/packages/camera/camera_android_camerax/lib/src/image_capture.dart b/packages/camera/camera_android_camerax/lib/src/image_capture.dart
index 76fd9c4ae5c0..b2f8671cd9f3 100644
--- a/packages/camera/camera_android_camerax/lib/src/image_capture.dart
+++ b/packages/camera/camera_android_camerax/lib/src/image_capture.dart
@@ -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 setTargetRotation(int rotation) =>
+ _api.setTargetRotationFromInstances(this, rotation);
+
/// Sets the flash mode to use for image capture.
Future 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 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 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 setFlashModeFromInstance(
+ Future 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 takePictureFromInstance(ImageCapture instance) async {
+ Future 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.');
diff --git a/packages/camera/camera_android_camerax/lib/src/preview.dart b/packages/camera/camera_android_camerax/lib/src/preview.dart
index f0568078bedb..8990313817b6 100644
--- a/packages/camera/camera_android_camerax/lib/src/preview.dart
+++ b/packages/camera/camera_android_camerax/lib/src/preview.dart
@@ -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 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 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 setSurfaceProviderFromInstance(Preview instance) async {
diff --git a/packages/camera/camera_android_camerax/lib/src/system_services.dart b/packages/camera/camera_android_camerax/lib/src/system_services.dart
index a2513e037662..b75a1cb98035 100644
--- a/packages/camera/camera_android_camerax/lib/src/system_services.dart
+++ b/packages/camera/camera_android_camerax/lib/src/system_services.dart
@@ -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
- deviceOrientationChangedStreamController =
- StreamController.broadcast();
-
/// Stream that emits the errors caused by camera usage on the native side.
static final StreamController cameraErrorStreamController =
StreamController.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) {
diff --git a/packages/camera/camera_android_camerax/lib/src/video_capture.dart b/packages/camera/camera_android_camerax/lib/src/video_capture.dart
index bb657cffc4b4..c3d4403a6ca7 100644
--- a/packages/camera/camera_android_camerax/lib/src/video_capture.dart
+++ b/packages/camera/camera_android_camerax/lib/src/video_capture.dart
@@ -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 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 setTargetRotation(int rotation) =>
+ _api.setTargetRotationFromInstances(this, rotation);
+
/// Gets the [Recorder] associated with this VideoCapture.
Future getOutput() {
return _api.getOutputFromInstance(this);
}
-
- late final VideoCaptureHostApiImpl _api;
}
/// Host API implementation of [VideoCapture].
@@ -76,6 +85,13 @@ class VideoCaptureHostApiImpl extends VideoCaptureHostApi {
.getInstanceWithWeakReference(videoCaptureId)!;
}
+ /// Dynamically sets the target rotation of [instance] to [rotation].
+ Future setTargetRotationFromInstances(
+ VideoCapture instance, int rotation) {
+ return setTargetRotation(
+ instanceManager.getIdentifier(instance)!, rotation);
+ }
+
/// Gets the [Recorder] associated with the provided [VideoCapture] instance.
Future getOutputFromInstance(VideoCapture instance) async {
final int? identifier = instanceManager.getIdentifier(instance);
diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
index 22827a803eb9..94285d148df4 100644
--- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
+++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart
@@ -211,19 +211,27 @@ abstract class SystemServicesHostApi {
@async
CameraPermissionsErrorData? requestCameraPermissions(bool enableAudio);
+ String getTempFilePath(String prefix, String suffix);
+}
+
+@FlutterApi()
+abstract class SystemServicesFlutterApi {
+ void onCameraError(String errorDescription);
+}
+
+@HostApi(dartHostTestHandler: 'TestDeviceOrientationManagerHostApi')
+abstract class DeviceOrientationManagerHostApi {
void startListeningForDeviceOrientationChange(
bool isFrontFacing, int sensorOrientation);
void stopListeningForDeviceOrientationChange();
- String getTempFilePath(String prefix, String suffix);
+ int getDefaultDisplayRotation();
}
@FlutterApi()
-abstract class SystemServicesFlutterApi {
+abstract class DeviceOrientationManagerFlutterApi {
void onDeviceOrientationChanged(String orientation);
-
- void onCameraError(String errorDescription);
}
@HostApi(dartHostTestHandler: 'TestPreviewHostApi')
@@ -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')
diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml
index 2cf3ac3b5d11..e4332e9e54e2 100644
--- a/packages/camera/camera_android_camerax/pubspec.yaml
+++ b/packages/camera/camera_android_camerax/pubspec.yaml
@@ -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"
diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
index 100d3040eb9b..2dbda1bba546 100644
--- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
+++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
@@ -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.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.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.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.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.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.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.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.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.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.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 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.value(defaultTargetRotation));
+
+ // Orientation is unlocked and plugin does not need to set default target
+ // rotation manually.
+ StreamSubscription 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);
+ });
}
diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart
index 6dfdd2b4824c..3657cb8ec118 100644
--- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart
+++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart
@@ -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 setTargetRotation(int? rotation) => (super.noSuchMethod(
+ Invocation.method(
+ #setTargetRotation,
+ [rotation],
+ ),
+ returnValue: _i16.Future.value(),
+ returnValueForMissingStub: _i16.Future.value(),
+ ) as _i16.Future);
+
@override
_i16.Future 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 setTargetRotation(int? rotation) => (super.noSuchMethod(
+ Invocation.method(
+ #setTargetRotation,
+ [rotation],
+ ),
+ returnValue: _i16.Future.value(),
+ returnValueForMissingStub: _i16.Future.value(),
+ ) as _i16.Future);
+
@override
_i16.Future 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 setTargetRotation(int? rotation) => (super.noSuchMethod(
+ Invocation.method(
+ #setTargetRotation,
+ [rotation],
+ ),
+ returnValue: _i16.Future.value(),
+ returnValueForMissingStub: _i16.Future.value(),
+ ) as _i16.Future);
+
@override
_i16.Future 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 setTargetRotation(int? rotation) => (super.noSuchMethod(
+ Invocation.method(
+ #setTargetRotation,
+ [rotation],
+ ),
+ returnValue: _i16.Future.value(),
+ returnValueForMissingStub: _i16.Future.value(),
+ ) as _i16.Future