Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.6.6

* Adds logic to support building a camera preview with Android `Surface`s not backed by a `SurfaceTexture`
to which CameraX cannot not automatically apply the transformation required to achieve the correct rotation.
* Adds fix for incorrect camera preview rotation on naturally landscape-oriented devices.
* Updates example app's minimum supported SDK version to Flutter 3.22/Dart 3.4.

## 0.6.5+6

* Updates Guava version to 33.2.1.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,30 @@ public class Camera2CameraInfoHostApiImpl implements Camera2CameraInfoHostApi {

/** Proxy for methods of {@link Camera2CameraInfo}. */
@VisibleForTesting
@OptIn(markerClass = ExperimentalCamera2Interop.class)
public static class Camera2CameraInfoProxy {

@NonNull
@OptIn(markerClass = ExperimentalCamera2Interop.class)
public Camera2CameraInfo createFrom(@NonNull CameraInfo cameraInfo) {
return Camera2CameraInfo.from(cameraInfo);
}

@NonNull
@OptIn(markerClass = ExperimentalCamera2Interop.class)
public Integer getSupportedHardwareLevel(@NonNull Camera2CameraInfo camera2CameraInfo) {
return camera2CameraInfo.getCameraCharacteristic(
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
}

@NonNull
@OptIn(markerClass = ExperimentalCamera2Interop.class)
public String getCameraId(@NonNull Camera2CameraInfo camera2CameraInfo) {
return camera2CameraInfo.getCameraId();
}

@NonNull
public Long getSensorOrientation(@NonNull Camera2CameraInfo camera2CameraInfo) {
return Long.valueOf(
camera2CameraInfo.getCameraCharacteristic(CameraCharacteristics.SENSOR_ORIENTATION));
}
}

/**
Expand Down Expand Up @@ -105,6 +109,12 @@ public String getCameraId(@NonNull Long identifier) {
return proxy.getCameraId(getCamera2CameraInfoInstance(identifier));
}

@Override
@NonNull
public Long getSensorOrientation(@NonNull Long identifier) {
return proxy.getSensorOrientation(getCamera2CameraInfoInstance(identifier));
}

private Camera2CameraInfo getCamera2CameraInfoInstance(@NonNull Long identifier) {
return Objects.requireNonNull(instanceManager.getInstance(identifier));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ interface DeviceOrientationChangeCallback {
* Starts listening to the device's sensors or UI for orientation updates.
*
* <p>When orientation information is updated, the callback method of the {@link
* DeviceOrientationChangeCallback} is called with the new orientation. This latest value can also
* be retrieved through the {@link #getVideoOrientation()} accessor.
* DeviceOrientationChangeCallback} is called with the new orientation.
*
* <p>If the device's ACCELEROMETER_ROTATION setting is enabled the {@link
* DeviceOrientationManager} will report orientation updates based on the sensor information. If
Expand Down Expand Up @@ -124,7 +123,7 @@ static void handleOrientationChange(
*/
// Configuration.ORIENTATION_SQUARE is deprecated.
@SuppressWarnings("deprecation")
@VisibleForTesting
@NonNull
PlatformChannel.DeviceOrientation getUIOrientation() {
final int rotation = getDefaultRotation();
final int orientation = activity.getResources().getConfiguration().orientation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ public void stopListeningForDeviceOrientationChange() {
* for instance for more information on how this default value is used.
*/
@Override
public @NonNull Long getDefaultDisplayRotation() {
@NonNull
public Long getDefaultDisplayRotation() {
int defaultRotation;
try {
defaultRotation = deviceOrientationManager.getDefaultRotation();
Expand All @@ -106,4 +107,11 @@ public void stopListeningForDeviceOrientationChange() {

return Long.valueOf(defaultRotation);
}

/** Gets current UI orientation based on the current device orientation and rotation. */
@Override
@NonNull
public String getUiOrientation() {
return serializeDeviceOrientation(deviceOrientationManager.getUIOrientation());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,9 @@ void requestCameraPermissions(
@NonNull
String getTempFilePath(@NonNull String prefix, @NonNull String suffix);

@NonNull
Boolean isPreviewPreTransformed();

/** The codec used by SystemServicesHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return SystemServicesHostApiCodec.INSTANCE;
Expand Down Expand Up @@ -1508,6 +1511,29 @@ public void error(Throwable error) {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.SystemServicesHostApi.isPreviewPreTransformed",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
Boolean output = api.isPreviewPreTransformed();
wrapped.add(0, output);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
Expand Down Expand Up @@ -1550,6 +1576,9 @@ void startListeningForDeviceOrientationChange(
@NonNull
Long getDefaultDisplayRotation();

@NonNull
String getUiOrientation();

/** The codec used by DeviceOrientationManagerHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
Expand Down Expand Up @@ -1634,6 +1663,29 @@ static void setup(
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.DeviceOrientationManagerHostApi.getUiOrientation",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
String output = api.getUiOrientation();
wrapped.add(0, output);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
Expand Down Expand Up @@ -4389,6 +4441,9 @@ public interface Camera2CameraInfoHostApi {
@NonNull
String getCameraId(@NonNull Long identifier);

@NonNull
Long getSensorOrientation(@NonNull Long identifier);

/** The codec used by Camera2CameraInfoHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
Expand Down Expand Up @@ -4481,6 +4536,33 @@ static void setup(
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.Camera2CameraInfoHostApi.getSensorOrientation",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number identifierArg = (Number) args.get(0);
try {
Long output =
api.getSensorOrientation(
(identifierArg == null) ? null : identifierArg.longValue());
wrapped.add(0, output);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
Expand Down Expand Up @@ -103,4 +104,18 @@ public String getTempFilePath(@NonNull String prefix, @NonNull String suffix) {
null);
}
}

/**
* Returns whether or not a {@code SurfaceTexture} backs the {@code Surface} provided to CameraX
* to build the camera preview. If it is backed by a {@code Surface}, then the transformation
* needed to correctly rotate the preview has already been applied.
*
* <p>This is determined by the engine, who uses {@code SurfaceTexture}s on Android SDKs 29 and
* below.
*/
@Override
@NonNull
public Boolean isPreviewPreTransformed() {
return Build.VERSION.SDK_INT <= 29;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,16 @@ public void getDefaultDisplayRotation_returnsExpectedRotation() {

assertEquals(hostApi.getDefaultDisplayRotation(), Long.valueOf(defaultRotation));
}

@Test
public void getUiOrientation_returnsExpectedOrientation() {
final DeviceOrientationManagerHostApiImpl hostApi =
new DeviceOrientationManagerHostApiImpl(mockBinaryMessenger, mockInstanceManager);
final DeviceOrientation uiOrientation = DeviceOrientation.LANDSCAPE_LEFT;

hostApi.deviceOrientationManager = mockDeviceOrientationManager;
when(mockDeviceOrientationManager.getUIOrientation()).thenReturn(uiOrientation);

assertEquals(hostApi.getUiOrientation(), uiOrientation.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package io.flutter.plugins.camerax;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
Expand All @@ -24,12 +26,16 @@
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
public class SystemServicesTest {
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

Expand Down Expand Up @@ -130,4 +136,28 @@ public void getTempFilePath_throwsRuntimeExceptionOnIOException() {

mockedStaticFile.close();
}

@Test
@Config(sdk = 28)
public void isPreviewPreTransformed_returnsTrueWhenRunningBelowSdk29() {
final SystemServicesHostApiImpl systemServicesHostApi =
new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager, mockContext);
assertTrue(systemServicesHostApi.isPreviewPreTransformed());
}

@Test
@Config(sdk = 29)
public void isPreviewPreTransformed_returnsTrueWhenRunningSdk29() {
final SystemServicesHostApiImpl systemServicesHostApi =
new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager, mockContext);
assertTrue(systemServicesHostApi.isPreviewPreTransformed());
}

@Test
@Config(sdk = 30)
public void isPreviewPreTransformed_returnsFalseWhenRunningAboveSdk29() {
final SystemServicesHostApiImpl systemServicesHostApi =
new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager, mockContext);
assertFalse(systemServicesHostApi.isPreviewPreTransformed());
}
}
4 changes: 2 additions & 2 deletions packages/camera/camera_android_camerax/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ description: Demonstrates how to use the camera_android_camerax plugin.
publish_to: 'none'

environment:
sdk: ^3.2.0
flutter: ">=3.16.0"
sdk: ^3.4.0
flutter: ">=3.22.0"

dependencies:
camera_android_camerax:
Expand Down
Loading