diff --git a/.ci.yaml b/.ci.yaml index 8bb66e0e11c..8d1412e2837 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -341,7 +341,7 @@ targets: - name: Linux_android custom_package_tests master recipe: packages/packages - timeout: 60 + timeout: 30 dimensions: kvm: "1" properties: @@ -367,7 +367,7 @@ targets: - name: Linux_android custom_package_tests stable recipe: packages/packages - timeout: 60 + timeout: 30 dimensions: kvm: "1" properties: diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 3c97cdddedb..f021cc38a7c 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -2e570ca393c844ada4d77b0210ce6a5bc20bcf82 +911aa7547ed732052f98b79f7b1584bd137bda43 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1cefdfd30ab..f66a07d3a06 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -121,14 +121,14 @@ updates: - "androidx.annotation:annotation" ignore: - dependency-name: "com.android.tools.build:gradle" - update-types: ["version-update:semver-patch"] + update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" - update-types: ["version-update:semver-patch"] + update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" - update-types: ["version-update:semver-patch"] + update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" - update-types: ["version-update:semver-patch"] + update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" - update-types: ["version-update:semver-patch"] + update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "io.mockk:mockk:*" - update-types: ["version-update:semver-patch"] + update-types: ["version-update:semver-minor", "version-update:semver-patch"] diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index 73359e455ad..bcec28fe5b9 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -140,7 +140,7 @@ class FakeController extends ValueNotifier void main() { group('RotatedBox (Android only)', () { testWidgets( - 'when recording in DeviceOrientaiton.portraitUp, rotatedBox should not be rotated', + 'when recording rotatedBox should turn according to recording orientation', ( WidgetTester tester, ) async { @@ -148,129 +148,17 @@ void main() { final FakeController controller = FakeController(); addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - isRecordingVideo: true, - deviceOrientation: DeviceOrientation.portraitDown, - lockedCaptureOrientation: - const Optional.fromNullable( - DeviceOrientation.landscapeRight), - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.portraitUp), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), - ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 0); - - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets( - 'when recording in DeviceOrientaiton.landscapeRight, rotatedBox should be rotated by one clockwise quarter turn', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - controller.value = controller.value.copyWith( - isInitialized: true, - isRecordingVideo: true, - deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: - const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeRight), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), - ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 1); - - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets( - 'when recording in DeviceOrientaiton.portraitDown, rotatedBox should be rotated by two clockwise quarter turns', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - isRecordingVideo: true, - deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: - const Optional.fromNullable( - DeviceOrientation.landscapeRight), - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.portraitDown), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), + isInitialized: true, + isRecordingVideo: true, + deviceOrientation: DeviceOrientation.portraitUp, + lockedCaptureOrientation: + const Optional.fromNullable( + DeviceOrientation.landscapeRight), + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), + previewSize: const Size(480, 640), ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 2); - - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets( - 'when recording in DeviceOrientaiton.landscapeLeft, rotatedBox should be rotated by three clockwise quarter turns', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - isRecordingVideo: true, - deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: - const Optional.fromNullable( - DeviceOrientation.landscapeRight), - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); await tester.pumpWidget( Directionality( @@ -288,7 +176,7 @@ void main() { }); testWidgets( - 'when orientation locked in DeviceOrientaiton.portaitUp, rotatedBox should not be rotated', + 'when orientation locked rotatedBox should turn according to locked orientation', ( WidgetTester tester, ) async { @@ -296,53 +184,16 @@ void main() { final FakeController controller = FakeController(); addTearDown(controller.dispose); - controller.value = controller.value.copyWith( - isInitialized: true, - deviceOrientation: DeviceOrientation.portraitDown, - lockedCaptureOrientation: - const Optional.fromNullable( - DeviceOrientation.portraitUp), - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), + isInitialized: true, + deviceOrientation: DeviceOrientation.portraitUp, + lockedCaptureOrientation: + const Optional.fromNullable( + DeviceOrientation.landscapeRight), + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), + previewSize: const Size(480, 640), ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 0); - - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets( - 'when orientation locked in DeviceOrientaiton.landscapeRight, rotatedBox should be rotated by one clockwise quarter turn', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - deviceOrientation: DeviceOrientation.portraitDown, - lockedCaptureOrientation: - const Optional.fromNullable( - DeviceOrientation.landscapeRight), - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); await tester.pumpWidget( Directionality( @@ -360,43 +211,7 @@ void main() { }); testWidgets( - 'when orientation locked in DeviceOrientaiton.portraitDown, rotatedBox should be rotated by two clockwise quarter turns', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: - const Optional.fromNullable( - DeviceOrientation.portraitDown), - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), - ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 2); - - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets( - 'when orientation locked in DeviceOrientaiton.landscapeRight, rotatedBox should be rotated by three clockwise quarter turns', + 'when not locked and not recording rotatedBox should turn according to device orientation', ( WidgetTester tester, ) async { @@ -404,50 +219,13 @@ void main() { final FakeController controller = FakeController(); addTearDown(controller.dispose); - controller.value = controller.value.copyWith( - isInitialized: true, - deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: - const Optional.fromNullable( - DeviceOrientation.landscapeRight), - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), + isInitialized: true, + deviceOrientation: DeviceOrientation.portraitUp, + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), + previewSize: const Size(480, 640), ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 1); - - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets( - 'when orientation not locked, not recording, and device orientation is portrait up, rotatedBox should not be rotated', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - deviceOrientation: DeviceOrientation.portraitUp, - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); await tester.pumpWidget( Directionality( @@ -463,105 +241,6 @@ void main() { debugDefaultTargetPlatformOverride = null; }); - - testWidgets( - 'when orientation not locked, not recording, and device orientation is landscape right, rotatedBox should be rotated by one clockwise quarter turn', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - deviceOrientation: DeviceOrientation.landscapeRight, - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), - ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 1); - - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets( - 'when orientation not locked, not recording, and device orientation is portrait down, rotatedBox should be rotated by two clockwise quarter turns', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - deviceOrientation: DeviceOrientation.portraitDown, - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.landscapeLeft), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), - ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 2); - - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets( - 'when orientation not locked, not recording, and device orientation is landscape left, rotatedBox should be rotated by three clockwise quarter turns', - ( - WidgetTester tester, - ) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - - final FakeController controller = FakeController(); - addTearDown(controller.dispose); - - controller.value = controller.value.copyWith( - isInitialized: true, - deviceOrientation: DeviceOrientation.landscapeLeft, - recordingOrientation: const Optional.fromNullable( - DeviceOrientation.portraitDown), - previewSize: const Size(480, 640) // preview size irrelevant to test - ); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: CameraPreview(controller), - ), - ); - expect(find.byType(RotatedBox), findsOneWidget); - - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.quarterTurns, 3); - - debugDefaultTargetPlatformOverride = null; - }); }, skip: kIsWeb); testWidgets('when not on Android there should not be a rotated box', @@ -570,9 +249,9 @@ void main() { final FakeController controller = FakeController(); addTearDown(controller.dispose); controller.value = controller.value.copyWith( - isInitialized: true, - previewSize: const Size(480, 640) // preview size irrelevant to test - ); + isInitialized: true, + previewSize: const Size(480, 640), + ); await tester.pumpWidget( Directionality( diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 0328dff3d54..7e190cb5ce3 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.6.14 - -* Fixes incorrect camera preview rotation. - ## 0.6.13 * Adds API support query for image streaming. 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 93f65cbc791..7adf02595ad 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 @@ -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 getCodec() { return SystemServicesHostApiCodec.INSTANCE; @@ -1508,6 +1511,29 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesHostApi.isPreviewPreTransformed", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.isPreviewPreTransformed(); + 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. */ @@ -1735,9 +1761,6 @@ void create( void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation); - @NonNull - Boolean surfaceProducerHandlesCropAndRotation(); - /** The codec used by PreviewHostApi. */ static @NonNull MessageCodec getCodec() { return PreviewHostApiCodec.INSTANCE; @@ -1875,29 +1898,6 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable PreviewHos channel.setMessageHandler(null); } } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.PreviewHostApi.surfaceProducerHandlesCropAndRotation", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - try { - Boolean output = api.surfaceProducerHandlesCropAndRotation(); - wrapped.add(0, output); - } 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/PreviewHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java index ffec50a22d4..92202dd74dc 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 @@ -155,10 +155,7 @@ String getProvideSurfaceErrorDescription(int resultCode) { public void releaseFlutterSurfaceTexture() { if (flutterSurfaceProducer != null) { flutterSurfaceProducer.release(); - return; } - throw new IllegalStateException( - "releaseFlutterSurfaceTexture() cannot be called if the flutterSurfaceProducer for the camera preview has not yet been initialized."); } /** Returns the resolution information for the specified {@link Preview}. */ @@ -182,16 +179,6 @@ public void setTargetRotation(@NonNull Long identifier, @NonNull Long rotation) preview.setTargetRotation(rotation.intValue()); } - @NonNull - @Override - public Boolean surfaceProducerHandlesCropAndRotation() { - if (flutterSurfaceProducer != null) { - return flutterSurfaceProducer.handlesCropAndRotation(); - } - throw new IllegalStateException( - "surfaceProducerHandlesCropAndRotation() cannot be called if the flutterSurfaceProducer for the camera preview has not yet been initialized."); - } - /** 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/SystemServicesHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java index d058d62fe22..138e9259e02 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 @@ -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; @@ -103,4 +104,18 @@ public String getTempFilePath(@NonNull String prefix, @NonNull String suffix) { null); } } + + /** + * Returns whether or not Impeller uses an {@code ImageReader} backend to provide a {@code + * Surface} to CameraX to build the preview. If it is backed by an {@code ImageReader}, then + * CameraX will not automatically apply the transformation needed to correct the preview. + * + *

This is determined by the engine, which approximately uses {@code SurfaceTexture}s on + * Android SDKs below 29. + */ + @Override + @NonNull + public Boolean isPreviewPreTransformed() { + return Build.VERSION.SDK_INT < 29; + } } 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 bc2577bc007..6fd43c2f9a3 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 @@ -263,20 +263,6 @@ public void setTargetRotation_makesCallToSetTargetRotation() { verify(mockPreview).setTargetRotation(targetRotation); } - @Test - public void - surfaceProducerHandlesCropAndRotation_returnsIfSurfaceProducerHandlesCropAndRotation() { - final PreviewHostApiImpl hostApi = - new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); - final TextureRegistry.SurfaceProducer mockSurfaceProducer = - mock(TextureRegistry.SurfaceProducer.class); - - hostApi.flutterSurfaceProducer = mockSurfaceProducer; - when(mockSurfaceProducer.handlesCropAndRotation()).thenReturn(true); - - assertEquals(hostApi.surfaceProducerHandlesCropAndRotation(), true); - } - // TODO(bparrishMines): Replace with inline calls to onSurfaceCleanup once available on stable; // see https://github.com/flutter/flutter/issues/16125. This separate method only exists to scope // the suppression. 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 fdfc1b224ff..52d02e67f65 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 @@ -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; @@ -31,6 +33,7 @@ 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 { @@ -133,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 = 28) + public void isPreviewPreTransformed_returnsTrueWhenRunningSdk28() { + final SystemServicesHostApiImpl systemServicesHostApi = + new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager, mockContext); + assertTrue(systemServicesHostApi.isPreviewPreTransformed()); + } + + @Test + @Config(sdk = 29) + public void isPreviewPreTransformed_returnsFalseWhenRunningAboveSdk28() { + final SystemServicesHostApiImpl systemServicesHostApi = + new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager, mockContext); + assertFalse(systemServicesHostApi.isPreviewPreTransformed()); + } } 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 8e61727cc61..31cde2fa0fc 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 @@ -47,7 +47,6 @@ import 'recording.dart'; import 'resolution_filter.dart'; import 'resolution_selector.dart'; import 'resolution_strategy.dart'; -import 'rotated_preview.dart'; import 'surface.dart'; import 'system_services.dart'; import 'use_case.dart'; @@ -239,18 +238,12 @@ class AndroidCameraCameraX extends CameraPlatform { late bool cameraIsFrontFacing; /// The camera sensor orientation. - /// - /// This can change if the camera being used changes. Also, it is independent - /// of the device orientation or user interface orientation. @visibleForTesting - late double sensorOrientationDegrees; - - /// Whether or not the Android surface producer automatically handles - /// correcting the rotation of camera previews for the device this plugin runs on. - late bool _handlesCropAndRotation; + late int sensorOrientation; - /// The initial orientation of the device when the camera is created. - late DeviceOrientation _initialDeviceOrientation; + /// Subscription for listening to changes in device orientation. + StreamSubscription? + _subscriptionForDeviceOrientationChanges; /// Returns list of all available cameras and their descriptions. @override @@ -387,10 +380,10 @@ class AndroidCameraCameraX extends CameraPlatform { // Retrieve info required for correcting the rotation of the camera preview // if necessary. - sensorOrientationDegrees = cameraDescription.sensorOrientation.toDouble(); - _handlesCropAndRotation = - await proxy.previewSurfaceProducerHandlesCropAndRotation(preview!); - _initialDeviceOrientation = await proxy.getUiOrientation(); + + final Camera2CameraInfo camera2CameraInfo = + await proxy.getCamera2CameraInfo(cameraInfo!); + sensorOrientation = await proxy.getSensorOrientation(camera2CameraInfo); return flutterSurfaceTextureId; } @@ -451,6 +444,7 @@ class AndroidCameraCameraX extends CameraPlatform { await liveCameraState?.removeObservers(); processCameraProvider?.unbindAll(); await imageAnalysis?.clearAnalyzer(); + await _subscriptionForDeviceOrientationChanges?.cancel(); } /// The camera has been initialized. @@ -842,30 +836,7 @@ class AndroidCameraCameraX extends CameraPlatform { ); } - final Widget preview = Texture(textureId: cameraId); - - if (_handlesCropAndRotation) { - return preview; - } - - final Stream deviceOrientationStream = - onDeviceOrientationChanged() - .map((DeviceOrientationChangedEvent e) => e.orientation); - if (cameraIsFrontFacing) { - return RotatedPreview.frontFacingCamera( - _initialDeviceOrientation, - deviceOrientationStream, - sensorOrientationDegrees: sensorOrientationDegrees, - child: preview, - ); - } else { - return RotatedPreview.backFacingCamera( - _initialDeviceOrientation, - deviceOrientationStream, - sensorOrientationDegrees: sensorOrientationDegrees, - child: preview, - ); - } + return Texture(textureId: cameraId); } /// Captures an image and returns the file where it was saved. 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 e830a288ce9..a9461eaaae0 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 @@ -1003,6 +1003,33 @@ class SystemServicesHostApi { return (replyList[0] as String?)!; } } + + Future isPreviewPreTransformed() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.isPreviewPreTransformed', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) 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 bool?)!; + } + } } abstract class SystemServicesFlutterApi { @@ -1329,33 +1356,6 @@ class PreviewHostApi { return; } } - - Future surfaceProducerHandlesCropAndRotation() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PreviewHostApi.surfaceProducerHandlesCropAndRotation', - codec, - binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) 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 bool?)!; - } - } } class VideoCaptureHostApi { 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 c5d92d29560..feae868ac40 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_proxy.dart @@ -70,8 +70,6 @@ class CameraXProxy { this.getCamera2CameraInfo = _getCamera2CameraInfo, this.getUiOrientation = _getUiOrientation, this.getSensorOrientation = _getSensorOrientation, - this.previewSurfaceProducerHandlesCropAndRotation = - _previewSurfaceProducerHandlesCropAndRotation, }); /// Returns a [ProcessCameraProvider] instance. @@ -202,11 +200,6 @@ class CameraXProxy { Future Function(Camera2CameraInfo camera2CameraInfo) getSensorOrientation; - /// Returns whether or not the preview's surface producer handles correctly - /// rotating the camera preview automatically. - Future Function(Preview preview) - previewSurfaceProducerHandlesCropAndRotation; - static Future _getProcessCameraProvider() { return ProcessCameraProvider.getInstance(); } @@ -362,9 +355,4 @@ class CameraXProxy { Camera2CameraInfo camera2CameraInfo) async { return camera2CameraInfo.getSensorOrientation(); } - - static Future _previewSurfaceProducerHandlesCropAndRotation( - Preview preview) async { - return preview.surfaceProducerHandlesCropAndRotation(); - } } diff --git a/packages/camera/camera_android_camerax/lib/src/preview.dart b/packages/camera/camera_android_camerax/lib/src/preview.dart index 76da65e4a81..8990313817b 100644 --- a/packages/camera/camera_android_camerax/lib/src/preview.dart +++ b/packages/camera/camera_android_camerax/lib/src/preview.dart @@ -86,12 +86,6 @@ class Preview extends UseCase { Future getResolutionInfo() { return _api.getResolutionInfoFromInstance(this); } - - /// Returns whether or not the Android surface producer automatically handles - /// correcting the rotation of camera previews for the device this plugin runs on. - Future surfaceProducerHandlesCropAndRotation() { - return _api.surfaceProducerHandlesCropAndRotationFromInstance(); - } } /// Host API implementation of [Preview]. @@ -162,10 +156,4 @@ class PreviewHostApiImpl extends PreviewHostApi { return resolutionInfo; } - - /// Returns whether or not the Android surface producer automatically handles - /// correcting the rotation of camera previews for the device this plugin runs on. - Future surfaceProducerHandlesCropAndRotationFromInstance() { - return surfaceProducerHandlesCropAndRotation(); - } } diff --git a/packages/camera/camera_android_camerax/lib/src/rotated_preview.dart b/packages/camera/camera_android_camerax/lib/src/rotated_preview.dart deleted file mode 100644 index 849de39013f..00000000000 --- a/packages/camera/camera_android_camerax/lib/src/rotated_preview.dart +++ /dev/null @@ -1,119 +0,0 @@ -// 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:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:meta/meta.dart'; - -/// Widget that rotates the camera preview to be upright according to the -/// current user interface orientation. -@internal -final class RotatedPreview extends StatefulWidget { - /// Creates [RotatedPreview] that will correct the preview - /// rotation assuming that the front camera is being used. - const RotatedPreview.frontFacingCamera( - this.initialDeviceOrientation, - this.deviceOrientation, { - required this.sensorOrientationDegrees, - required this.child, - super.key, - }) : facingSign = 1; - - /// Creates [RotatedPreview] that will correct the preview - /// rotation assuming that the back camera is being used. - const RotatedPreview.backFacingCamera( - this.initialDeviceOrientation, - this.deviceOrientation, { - required this.child, - required this.sensorOrientationDegrees, - super.key, - }) : facingSign = -1; - - /// The initial orientation of the device when the camera is created. - final DeviceOrientation initialDeviceOrientation; - - /// The orientation of the device using the camera. - final Stream deviceOrientation; - - /// The orienation of the camera sensor in degrees. - final double sensorOrientationDegrees; - - /// The camera preview [Widget] to rotate. - final Widget child; - - /// Value used to calculate the correct preview rotation. - /// - /// 1 if the camera is front facing; -1 if the camera is back facing. - final int facingSign; - - @override - State createState() => _RotatedPreviewState(); -} - -final class _RotatedPreviewState extends State { - late DeviceOrientation deviceOrientation; - late StreamSubscription deviceOrientationSubscription; - - @override - void initState() { - deviceOrientation = widget.initialDeviceOrientation; - deviceOrientationSubscription = - widget.deviceOrientation.listen((DeviceOrientation event) { - // Ensure that we aren't updating the state if the widget is being destroyed. - if (!mounted) { - return; - } - setState(() { - deviceOrientation = event; - }); - }); - super.initState(); - } - - double _computeRotationDegrees( - DeviceOrientation orientation, { - required double sensorOrientationDegrees, - required int sign, - }) { - final double deviceOrientationDegrees = switch (orientation) { - DeviceOrientation.portraitUp => 0, - DeviceOrientation.landscapeRight => 90, - DeviceOrientation.portraitDown => 180, - DeviceOrientation.landscapeLeft => 270, - }; - - // Rotate the camera preview according to - // https://developer.android.com/media/camera/camera2/camera-preview#orientation_calculation. - double rotationDegrees = - (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % - 360; - - // Then, subtract the rotation already applied in the CameraPreview widget - // (see camera/camera/lib/src/camera_preview.dart). - rotationDegrees -= deviceOrientationDegrees; - - return rotationDegrees; - } - - @override - void dispose() { - deviceOrientationSubscription.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final double rotationDegrees = _computeRotationDegrees( - deviceOrientation, - sensorOrientationDegrees: widget.sensorOrientationDegrees, - sign: widget.facingSign, - ); - return RotatedBox( - quarterTurns: rotationDegrees ~/ 90, - child: widget.child, - ); - } -} 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 b75a1cb9803..5f59bd2f4c6 100644 --- a/packages/camera/camera_android_camerax/lib/src/system_services.dart +++ b/packages/camera/camera_android_camerax/lib/src/system_services.dart @@ -48,6 +48,21 @@ class SystemServices { SystemServicesHostApi(binaryMessenger: binaryMessenger); return api.getTempFilePath(prefix, suffix); } + + /// Returns whether or not the Android Surface used to display the camera + /// preview is backed by a SurfaceTexture, to which the transformation to + /// correctly rotate the preview has been applied. + /// + /// This is used to determine the correct rotation of the camera preview + /// because Surfaces not backed by a SurfaceTexture are not transformed by + /// CameraX to the expected rotation based on that of the device and must + /// be corrected by the plugin. + static Future isPreviewPreTransformed( + {BinaryMessenger? binaryMessenger}) { + final SystemServicesHostApi api = + SystemServicesHostApi(binaryMessenger: binaryMessenger); + return api.isPreviewPreTransformed(); + } } /// Host API implementation of [SystemServices]. diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index a6e4eded23a..949830db3e8 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -260,6 +260,8 @@ abstract class SystemServicesHostApi { CameraPermissionsErrorData? requestCameraPermissions(bool enableAudio); String getTempFilePath(String prefix, String suffix); + + bool isPreviewPreTransformed(); } @FlutterApi() @@ -295,8 +297,6 @@ abstract class PreviewHostApi { ResolutionInfo getResolutionInfo(int identifier); void setTargetRotation(int identifier, int rotation); - - bool surfaceProducerHandlesCropAndRotation(); } @HostApi(dartHostTestHandler: 'TestVideoCaptureHostApi') diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index d8fa61298fc..fb7e6f0e317 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.6.14 +version: 0.6.13 environment: sdk: ^3.6.0 @@ -29,6 +29,8 @@ dev_dependencies: build_runner: ^2.2.0 flutter_test: sdk: flutter + integration_test: + sdk: flutter leak_tracker_flutter_testing: any mockito: ^5.4.4 pigeon: ^9.1.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 2e4a5c83af7..89e201b3b32 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 @@ -50,7 +50,7 @@ import 'package:camera_android_camerax/src/zoom_state.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart' show DeviceOrientation, PlatformException, Uint8List; -import 'package:flutter/widgets.dart' show BuildContext, Size; +import 'package:flutter/widgets.dart' show BuildContext, Size, Texture, Widget; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -201,8 +201,6 @@ void main() { Future.value(MockCamera2CameraInfo()), getUiOrientation: () => Future.value(DeviceOrientation.portraitUp), - previewSurfaceProducerHandlesCropAndRotation: (_) => - Future.value(false), ); /// CameraXProxy for testing exposure and focus related controls. @@ -921,9 +919,7 @@ void main() { expect(camera.recorder!.qualitySelector, isNull); }); - test( - 'createCamera sets sensor orientation, handlesCropAndRotation, initialDeviceOrientation as expected', - () async { + test('createCamera sets sensor orientation as expected', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const CameraLensDirection testLensDirection = CameraLensDirection.back; const int testSensorOrientation = 270; @@ -933,7 +929,6 @@ void main() { sensorOrientation: testSensorOrientation); const bool enableAudio = true; const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh; - const bool testHandlesCropAndRotation = true; const DeviceOrientation testUiOrientation = DeviceOrientation.portraitDown; // Mock/Detached objects for (typically attached) objects created by @@ -953,8 +948,6 @@ void main() { getProxyForTestingResolutionPreset(mockProcessCameraProvider); camera.proxy.getSensorOrientation = (_) async => Future.value(testSensorOrientation); - camera.proxy.previewSurfaceProducerHandlesCropAndRotation = - (_) async => Future.value(testHandlesCropAndRotation); camera.proxy.getUiOrientation = () async => Future.value(testUiOrientation); @@ -967,7 +960,7 @@ void main() { await camera.createCamera(testCameraDescription, testResolutionPreset, enableAudio: enableAudio); - expect(camera.sensorOrientationDegrees, testSensorOrientation); + expect(camera.sensorOrientation, testSensorOrientation); }); test( @@ -1303,8 +1296,6 @@ void main() { expect(camera.cameraControl, equals(mockCameraControl)); }); - // Further `buildPreview` testing concerning the Widget that it returns is - // located in preview_rotation_test.dart. test( 'buildPreview throws an exception if the preview is not bound to the lifecycle', () async { @@ -1319,6 +1310,22 @@ void main() { () => camera.buildPreview(cameraId), throwsA(isA())); }); + test( + 'buildPreview returns a Texture once the preview is bound to the lifecycle if it is backed by a SurfaceTexture', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 37; + + // Tell camera that createCamera has been called and thus, preview has been + // bound to the lifecycle of the camera. + camera.previewInitiallyBound = true; + + final Widget widget = camera.buildPreview(cameraId); + + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); + }); + group('video recording', () { test( 'startVideoCapturing binds video capture use case, updates saved camera instance and its properties, and starts the recording', 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 fc838a461bb..316b1275969 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 @@ -1134,17 +1134,6 @@ class MockPreview extends _i1.Mock implements _i35.Preview { ), )), ) as _i17.Future<_i10.ResolutionInfo>); - - @override - _i17.Future surfaceProducerHandlesCropAndRotation() => - (super.noSuchMethod( - Invocation.method( - #surfaceProducerHandlesCropAndRotation, - [], - ), - returnValue: _i17.Future.value(false), - returnValueForMissingStub: _i17.Future.value(false), - ) as _i17.Future); } /// A class which mocks [ProcessCameraProvider]. @@ -1421,6 +1410,16 @@ class MockTestSystemServicesHostApi extends _i1.Mock ), ), ) as String); + + @override + bool isPreviewPreTransformed() => (super.noSuchMethod( + Invocation.method( + #isPreviewPreTransformed, + [], + ), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); } /// A class which mocks [VideoCapture]. diff --git a/packages/camera/camera_android_camerax/test/capture_request_options_test.dart b/packages/camera/camera_android_camerax/test/capture_request_options_test.dart index c048aae2297..5649151af92 100644 --- a/packages/camera/camera_android_camerax/test/capture_request_options_test.dart +++ b/packages/camera/camera_android_camerax/test/capture_request_options_test.dart @@ -49,7 +49,7 @@ void main() { argThat(isA()), argThat(isA>()), )); - }, skip: 'Flaky test: https://github.com/flutter/flutter/issues/164132'); + }); test( 'create makes call on the Java side as expected for suppported null capture request options', diff --git a/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart b/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart index afb31c1ab72..8a7dcdc6c11 100644 --- a/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart +++ b/packages/camera/camera_android_camerax/test/focus_metering_action_test.dart @@ -45,8 +45,7 @@ void main() { verifyNever(mockApi.create(argThat(isA()), argThat(isA>()), argThat(isA()))); - }, skip: 'Flaky test: https://github.com/flutter/flutter/issues/164132'); - + }); test('create calls create on the Java side', () { final MockTestFocusMeteringActionHostApi mockApi = MockTestFocusMeteringActionHostApi(); diff --git a/packages/camera/camera_android_camerax/test/image_analysis_test.dart b/packages/camera/camera_android_camerax/test/image_analysis_test.dart index 0da5fd1f720..df220357508 100644 --- a/packages/camera/camera_android_camerax/test/image_analysis_test.dart +++ b/packages/camera/camera_android_camerax/test/image_analysis_test.dart @@ -48,8 +48,7 @@ void main() { verifyNever(mockApi.create(argThat(isA()), argThat(isA()), argThat(isA()))); - }, skip: 'Flaky test: https://github.com/flutter/flutter/issues/164132'); - + }); test('create calls create on the Java side', () { final MockTestImageAnalysisHostApi mockApi = MockTestImageAnalysisHostApi(); diff --git a/packages/camera/camera_android_camerax/test/image_capture_test.dart b/packages/camera/camera_android_camerax/test/image_capture_test.dart index aae531e7073..56cbd8fcc18 100644 --- a/packages/camera/camera_android_camerax/test/image_capture_test.dart +++ b/packages/camera/camera_android_camerax/test/image_capture_test.dart @@ -43,7 +43,7 @@ void main() { verifyNever(mockApi.create(argThat(isA()), argThat(isA()), argThat(isA()), argThat(isA()))); - }, skip: 'Flaky test: https://github.com/flutter/flutter/issues/164132'); + }); test('create calls create on the Java side', () async { final MockTestImageCaptureHostApi mockApi = MockTestImageCaptureHostApi(); diff --git a/packages/camera/camera_android_camerax/test/metering_point_test.dart b/packages/camera/camera_android_camerax/test/metering_point_test.dart index 24c899df6a5..5aa92164377 100644 --- a/packages/camera/camera_android_camerax/test/metering_point_test.dart +++ b/packages/camera/camera_android_camerax/test/metering_point_test.dart @@ -41,7 +41,7 @@ void main() { verifyNever(mockApi.create(argThat(isA()), argThat(isA()), argThat(isA()), argThat(isA()), argThat(isA()))); - }, skip: 'Flaky test: https://github.com/flutter/flutter/issues/164132'); + }); test('create calls create on the Java side', () async { final MockTestMeteringPointHostApi mockApi = diff --git a/packages/camera/camera_android_camerax/test/preview_rotation_test.dart b/packages/camera/camera_android_camerax/test/preview_rotation_test.dart deleted file mode 100644 index e06d16cbc0f..00000000000 --- a/packages/camera/camera_android_camerax/test/preview_rotation_test.dart +++ /dev/null @@ -1,637 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:camera_android_camerax/camera_android_camerax.dart'; -import 'package:camera_android_camerax/src/camera_selector.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/fallback_strategy.dart'; -import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; - -import 'android_camera_camerax_test.mocks.dart'; - -// Constants to map clockwise degree rotations to quarter turns: -const int _90DegreesClockwise = 1; -const int _270DegreesClockwise = 3; - -void main() { - /// Sets up mock CameraSelector and mock ProcessCameraProvider used to - /// select test camera when `availableCameras` is called. - /// - /// Also mocks a call for mock ProcessCameraProvider that is irrelevant - /// to this test. - /// - /// Returns mock ProcessCameraProvider that is used to select test camera. - MockProcessCameraProvider - setUpMockCameraSelectorAndMockProcessCameraProviderForSelectingTestCamera( - {required MockCameraSelector mockCameraSelector, - required int sensorRotationDegrees}) { - final MockProcessCameraProvider mockProcessCameraProvider = - MockProcessCameraProvider(); - final MockCameraInfo mockCameraInfo = MockCameraInfo(); - final MockCamera mockCamera = MockCamera(); - - // Mock retrieving available test camera. - when(mockProcessCameraProvider.bindToLifecycle(any, any)) - .thenAnswer((_) async => mockCamera); - when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); - when(mockProcessCameraProvider.getAvailableCameraInfos()) - .thenAnswer((_) async => [mockCameraInfo]); - when(mockCameraSelector.filter([mockCameraInfo])) - .thenAnswer((_) async => [mockCameraInfo]); - when(mockCameraInfo.getSensorRotationDegrees()) - .thenAnswer((_) async => sensorRotationDegrees); - - // Mock additional ProcessCameraProvider operation that is irrelevant - // for the tests in this file. - when(mockCameraInfo.getCameraState()) - .thenAnswer((_) async => MockLiveCameraState()); - - return mockProcessCameraProvider; - } - - /// Returns CameraXProxy used to mock all calls to native Android in - /// the `availableCameras` and `createCameraWithSettings` methods. - CameraXProxy getProxyForCreatingTestCamera( - {required MockProcessCameraProvider mockProcessCameraProvider, - required CameraSelector Function(int) createCameraSelector, - required bool handlesCropAndRotation, - required Future Function() getUiOrientation}) => - CameraXProxy( - getProcessCameraProvider: () async => mockProcessCameraProvider, - createCameraSelector: createCameraSelector, - previewSurfaceProducerHandlesCropAndRotation: (_) => - Future.value(handlesCropAndRotation), - getUiOrientation: getUiOrientation, - createPreview: (_, __) => MockPreview(), - createImageCapture: (_, __) => MockImageCapture(), - createRecorder: (_) => MockRecorder(), - createVideoCapture: (_) async => MockVideoCapture(), - createImageAnalysis: (_, __) => MockImageAnalysis(), - createResolutionStrategy: ( - {bool highestAvailable = false, - Size? boundSize, - int? fallbackRule}) => - MockResolutionStrategy(), - createResolutionSelector: (_, __, ___) => MockResolutionSelector(), - createFallbackStrategy: ( - {required VideoQuality quality, - required VideoResolutionFallbackRule fallbackRule}) => - MockFallbackStrategy(), - createQualitySelector: ( - {required VideoQuality videoQuality, - required FallbackStrategy fallbackStrategy}) => - MockQualitySelector(), - createCameraStateObserver: (_) => MockObserver(), - requestCameraPermissions: (_) => Future.value(), - startListeningForDeviceOrientationChange: (_, __) {}, - setPreviewSurfaceProvider: (_) => Future.value( - 3), // 3 is a random Flutter SurfaceTexture ID for testing - createAspectRatioStrategy: (int aspectRatio, int fallbackRule) => - MockAspectRatioStrategy(), - createResolutionFilterWithOnePreferredSize: - (Size preferredResolution) => MockResolutionFilter(), - ); - - /// Returns function that a CameraXProxy can use to select the front camera. - MockCameraSelector Function(int cameraSelectorLensDirection) - createCameraSelectorForFrontCamera( - MockCameraSelector mockCameraSelector) { - return (int cameraSelectorLensDirection) { - switch (cameraSelectorLensDirection) { - case CameraSelector.lensFacingFront: - return mockCameraSelector; - default: - return MockCameraSelector(); - } - }; - } - - /// Returns function that a CameraXProxy can use to select the back camera. - MockCameraSelector Function(int cameraSelectorLensDirection) - createCameraSelectorForBackCamera(MockCameraSelector mockCameraSelector) { - return (int cameraSelectorLensDirection) { - switch (cameraSelectorLensDirection) { - case CameraSelector.lensFacingBack: - return mockCameraSelector; - default: - return MockCameraSelector(); - } - }; - } - - /// Error message for detecting an incorrect preview rotation. - String getExpectedRotationTestFailureReason( - int expectedQuarterTurns, int actualQuarterTurns) => - 'Expected the preview to be rotated by $expectedQuarterTurns quarter turns (which is ${expectedQuarterTurns * 90} degrees clockwise) but instead was rotated $actualQuarterTurns quarter turns.'; - - testWidgets( - 'when handlesCropAndRotation is true, the preview is an unrotated Texture', - (WidgetTester tester) async { - final AndroidCameraCameraX camera = AndroidCameraCameraX(); - const int cameraId = 537; - const MediaSettings testMediaSettings = - MediaSettings(); // media settings irrelevant for test - - // Set up test camera (specifics irrelevant for this test) and - // tell camera that handlesCropAndRotation is true. - final MockCameraSelector mockCameraSelector = MockCameraSelector(); - final MockProcessCameraProvider mockProcessCameraProvider = - setUpMockCameraSelectorAndMockProcessCameraProviderForSelectingTestCamera( - mockCameraSelector: mockCameraSelector, - sensorRotationDegrees: /* irrelevant for test */ 90); - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProvider, - createCameraSelector: (_) => mockCameraSelector, - handlesCropAndRotation: true, - /* irrelevant for test */ getUiOrientation: () => - Future.value(DeviceOrientation.landscapeLeft)); - - // Get and create test camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture was built. - final Texture texture = tester.widget(find.byType(Texture)); - expect(texture.textureId, cameraId); - - // Verify RotatedBox was not built and thus, the Texture is not rotated. - expect(() => tester.widget(find.byType(RotatedBox)), - throwsStateError); - }); - - group('when handlesCropAndRotation is false,', () { - // Test that preview rotation responds to initial device orientation: - group('sensor orientation degrees is 270, camera is front facing,', () { - late AndroidCameraCameraX camera; - late int cameraId; - late MockCameraSelector mockFrontCameraSelector; - late MockCameraSelector Function(int cameraSelectorLensDirection) - proxyCreateCameraSelectorForFrontCamera; - late MockProcessCameraProvider mockProcessCameraProviderForFrontCamera; - late MediaSettings testMediaSettings; - - setUp(() { - camera = AndroidCameraCameraX(); - cameraId = 27; - - // Create and set up mock CameraSelector and mock ProcessCameraProvider for test front camera - // with sensor orientation degrees 270. - mockFrontCameraSelector = MockCameraSelector(); - proxyCreateCameraSelectorForFrontCamera = - createCameraSelectorForFrontCamera(mockFrontCameraSelector); - mockProcessCameraProviderForFrontCamera = - setUpMockCameraSelectorAndMockProcessCameraProviderForSelectingTestCamera( - mockCameraSelector: mockFrontCameraSelector, - sensorRotationDegrees: 270); - - // Media settings to create camera; irrelevant for test. - testMediaSettings = const MediaSettings(); - }); - - testWidgets( - 'initial device orientation fixed to DeviceOrientation.portraitUp, then the preview Texture is rotated 270 degrees clockwise', - (WidgetTester tester) async { - // Set up test to use front camera, tell camera that handlesCropAndRotation is false, - // set camera initial device orientation to portrait up. - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProviderForFrontCamera, - createCameraSelector: proxyCreateCameraSelectorForFrontCamera, - handlesCropAndRotation: false, - getUiOrientation: () => - Future.value(DeviceOrientation.portraitUp)); - - // Get and create test front camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture is rotated by ((270 - 0 * 1 + 360) % 360) - 0 = 270 degrees. - const int expectedQuarterTurns = _270DegreesClockwise; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(rotatedBox.quarterTurns, expectedQuarterTurns, - reason: getExpectedRotationTestFailureReason( - expectedQuarterTurns, rotatedBox.quarterTurns)); - }); - - testWidgets( - 'initial device orientation fixed to DeviceOrientation.landscapeRight, then the preview Texture is rotated 90 degrees clockwise', - (WidgetTester tester) async { - // Set up test to use front camera, tell camera that handlesCropAndRotation is false, - // set camera initial device orientation to landscape right. - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProviderForFrontCamera, - createCameraSelector: proxyCreateCameraSelectorForFrontCamera, - handlesCropAndRotation: false, - getUiOrientation: () => Future.value( - DeviceOrientation.landscapeRight)); - - // Get and create test front camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture is rotated by ((90 - 270 * 1 + 360) % 360) - 90 = 90 degrees. - const int expectedQuarterTurns = _90DegreesClockwise; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(rotatedBox.quarterTurns, expectedQuarterTurns, - reason: getExpectedRotationTestFailureReason( - expectedQuarterTurns, rotatedBox.quarterTurns)); - }); - - testWidgets( - 'initial device orientation fixed to DeviceOrientation.portraitDown, then the preview Texture is rotated 270 degrees clockwise', - (WidgetTester tester) async { - // Set up test to use front camera, tell camera that handlesCropAndRotation is false, - // set camera initial device orientation to portrait down. - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProviderForFrontCamera, - createCameraSelector: proxyCreateCameraSelectorForFrontCamera, - handlesCropAndRotation: false, - getUiOrientation: () => Future.value( - DeviceOrientation.portraitDown)); - - // Get and create test front camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture is rotated by ((270 - 180 * 1 + 360) % 360) - 180 = -90 degrees clockwise = 90 degrees counterclockwise = 270 degrees. - const int expectedQuarterTurns = _270DegreesClockwise; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - final int clockwiseQuarterTurns = rotatedBox.quarterTurns + 4; - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(clockwiseQuarterTurns, expectedQuarterTurns, - reason: getExpectedRotationTestFailureReason( - expectedQuarterTurns, rotatedBox.quarterTurns)); - }); - - testWidgets( - 'initial device orientation fixed to DeviceOrientation.landscapeLeft, then the preview Texture is rotated 90 degrees clockwise', - (WidgetTester tester) async { - // Set up test to use front camera, tell camera that handlesCropAndRotation is false, - // set camera initial device orientation to landscape left. - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProviderForFrontCamera, - createCameraSelector: proxyCreateCameraSelectorForFrontCamera, - handlesCropAndRotation: false, - getUiOrientation: () => Future.value( - DeviceOrientation.landscapeLeft)); - - // Get and create test front camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture is rotated by ((270 - 270 * 1 + 360) % 360) - 270 = -270 degrees clockwise = 270 degrees counterclockwise = 90 degrees. - const int expectedQuarterTurns = _90DegreesClockwise; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - final int clockwiseQuarterTurns = rotatedBox.quarterTurns + 4; - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(clockwiseQuarterTurns, expectedQuarterTurns, - reason: getExpectedRotationTestFailureReason( - expectedQuarterTurns, rotatedBox.quarterTurns)); - }); - }); - - testWidgets( - 'sensor orientation degrees is 90, camera is front facing, then the preview Texture rotates correctly as the device orientation rotates', - (WidgetTester tester) async { - final AndroidCameraCameraX camera = AndroidCameraCameraX(); - const int cameraId = 3372; - - // Create and set up mock CameraSelector and mock ProcessCameraProvider for test front camera - // with sensor orientation degrees 90. - final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); - final MockCameraSelector Function(int cameraSelectorLensDirection) - proxyCreateCameraSelectorForFrontCamera = - createCameraSelectorForFrontCamera(mockFrontCameraSelector); - final MockProcessCameraProvider mockProcessCameraProviderForFrontCamera = - setUpMockCameraSelectorAndMockProcessCameraProviderForSelectingTestCamera( - mockCameraSelector: mockFrontCameraSelector, - sensorRotationDegrees: 90); - - // Media settings to create camera; irrelevant for test. - const MediaSettings testMediaSettings = MediaSettings(); - - // Set up test to use front camera and tell camera that handlesCropAndRotation is false, - // set camera initial device orientation to landscape left. - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProviderForFrontCamera, - createCameraSelector: proxyCreateCameraSelectorForFrontCamera, - handlesCropAndRotation: false, - getUiOrientation: /* initial device orientation irrelevant for test */ - () => Future.value( - DeviceOrientation.landscapeLeft)); - - // Get and create test front camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Calculated according to: - // ((90 - currentDeviceOrientation * 1 + 360) % 360) - currentDeviceOrientation. - final Map expectedRotationPerDeviceOrientation = - { - DeviceOrientation.portraitUp: _90DegreesClockwise, - DeviceOrientation.landscapeRight: _270DegreesClockwise, - DeviceOrientation.portraitDown: _90DegreesClockwise, - DeviceOrientation.landscapeLeft: _270DegreesClockwise, - }; - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - for (final DeviceOrientation currentDeviceOrientation - in expectedRotationPerDeviceOrientation.keys) { - final DeviceOrientationChangedEvent testEvent = - DeviceOrientationChangedEvent(currentDeviceOrientation); - DeviceOrientationManager.deviceOrientationChangedStreamController - .add(testEvent); - - await tester.pumpAndSettle(); - - // Verify Texture is rotated by expected clockwise degrees. - final int expectedQuarterTurns = - expectedRotationPerDeviceOrientation[currentDeviceOrientation]!; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - final int clockwiseQuarterTurns = rotatedBox.quarterTurns < 0 - ? rotatedBox.quarterTurns + 4 - : rotatedBox.quarterTurns; - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(clockwiseQuarterTurns, expectedQuarterTurns, - reason: - 'When the device orientation is $currentDeviceOrientation, expected the preview to be rotated by $expectedQuarterTurns quarter turns (which is ${expectedQuarterTurns * 90} degrees clockwise) but instead was rotated ${rotatedBox.quarterTurns} quarter turns.'); - } - - await DeviceOrientationManager.deviceOrientationChangedStreamController - .close(); - }); - - // Test the preview rotation responds to the two most common sensor orientations for Android phone cameras; see - // https://developer.android.com/media/camera/camera2/camera-preview#camera_orientation. - group( - 'initial device orientation is DeviceOrientation.landscapeLeft, camera is back facing,', - () { - late AndroidCameraCameraX camera; - late int cameraId; - late MockCameraSelector mockBackCameraSelector; - late MockCameraSelector Function(int cameraSelectorLensDirection) - proxyCreateCameraSelectorForBackCamera; - late MediaSettings testMediaSettings; - late DeviceOrientation testInitialDeviceOrientation; - - setUp(() { - camera = AndroidCameraCameraX(); - cameraId = 347; - - // Set test camera initial device orientation for test. - testInitialDeviceOrientation = DeviceOrientation.landscapeLeft; - - // Create and set up mock CameraSelector and mock ProcessCameraProvider for test back camera - // with sensor orientation degrees 270. - mockBackCameraSelector = MockCameraSelector(); - proxyCreateCameraSelectorForBackCamera = - createCameraSelectorForBackCamera(mockBackCameraSelector); - - testMediaSettings = const MediaSettings(); - }); - - testWidgets( - 'sensor orientation degrees is 90, then the preview Texture is rotated 90 degrees clockwise', - (WidgetTester tester) async { - // Create mock ProcessCameraProvider that will acknowledge that the test back camera with sensor orientation degrees - // 90 is available. - final MockProcessCameraProvider mockProcessCameraProviderForBackCamera = - setUpMockCameraSelectorAndMockProcessCameraProviderForSelectingTestCamera( - mockCameraSelector: mockBackCameraSelector, - sensorRotationDegrees: 90); - - // Set up test to use back camera, tell camera that handlesCropAndRotation is false, - // set camera initial device orientation to landscape left. - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProviderForBackCamera, - createCameraSelector: proxyCreateCameraSelectorForBackCamera, - handlesCropAndRotation: false, - getUiOrientation: () => - Future.value(testInitialDeviceOrientation)); - - // Get and create test back camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture is rotated by ((90 - 270 * -1 + 360) % 360) - 270 = -270 degrees clockwise = 270 degrees counterclockwise = 90 degrees clockwise. - const int expectedQuarterTurns = _90DegreesClockwise; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - final int clockwiseQuarterTurns = rotatedBox.quarterTurns + 4; - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(clockwiseQuarterTurns, expectedQuarterTurns, - reason: getExpectedRotationTestFailureReason( - expectedQuarterTurns, rotatedBox.quarterTurns)); - }); - - testWidgets( - 'sensor orientation degrees is 270, then the preview Texture is rotated 270 degrees clockwise', - (WidgetTester tester) async { - // Create mock ProcessCameraProvider that will acknowledge that the test back camera with sensor orientation degrees - // 270 is available. - final MockProcessCameraProvider mockProcessCameraProviderForBackCamera = - setUpMockCameraSelectorAndMockProcessCameraProviderForSelectingTestCamera( - mockCameraSelector: mockBackCameraSelector, - sensorRotationDegrees: 270); - - // Set up test to use back camera, tell camera that handlesCropAndRotation is false, - // set camera initial device orientation to landscape left. - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProviderForBackCamera, - createCameraSelector: proxyCreateCameraSelectorForBackCamera, - handlesCropAndRotation: false, - getUiOrientation: () => - Future.value(testInitialDeviceOrientation)); - - // Get and create test back camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture is rotated by ((270 - 270 * -1 + 360) % 360) - 270 = -90 degrees clockwise = 90 degrees counterclockwise = 270 degrees clockwise. - const int expectedQuarterTurns = _270DegreesClockwise; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - final int clockwiseQuarterTurns = rotatedBox.quarterTurns + 4; - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(clockwiseQuarterTurns, expectedQuarterTurns, - reason: getExpectedRotationTestFailureReason( - expectedQuarterTurns, rotatedBox.quarterTurns)); - }); - }); - - // Test the preview rotation responds to the camera being front or back facing: - group( - 'initial device orientation is DeviceOrientation.landscapeRight, sensor orientation degrees is 90,', - () { - late AndroidCameraCameraX camera; - late int cameraId; - late MediaSettings testMediaSettings; - late DeviceOrientation testInitialDeviceOrientation; - late int testSensorOrientation; - - setUp(() { - camera = AndroidCameraCameraX(); - cameraId = 317; - - // Set test camera initial device orientation and sensor orientation for test. - testInitialDeviceOrientation = DeviceOrientation.landscapeRight; - testSensorOrientation = 90; - - // Media settings to create camera; irrelevant for test. - testMediaSettings = const MediaSettings(); - }); - - testWidgets( - 'camera is front facing, then the preview Texture is rotated 270 degrees clockwise', - (WidgetTester tester) async { - // Set up test front camera with sensor orientation degrees 90. - final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); - final MockProcessCameraProvider mockProcessCameraProvider = - setUpMockCameraSelectorAndMockProcessCameraProviderForSelectingTestCamera( - mockCameraSelector: mockFrontCameraSelector, - sensorRotationDegrees: testSensorOrientation); - - // Set up front camera selection and initial device orientation as landscape right. - final MockCameraSelector Function(int cameraSelectorLensDirection) - proxyCreateCameraSelectorForFrontCamera = - createCameraSelectorForFrontCamera(mockFrontCameraSelector); - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProvider, - createCameraSelector: proxyCreateCameraSelectorForFrontCamera, - handlesCropAndRotation: false, - getUiOrientation: () => - Future.value(testInitialDeviceOrientation)); - - // Get and create test camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture is rotated by ((90 - 90 * 1 + 360) % 360) - 90 = -90 degrees clockwise = 90 degrees counterclockwise = 270 degrees clockwise. - const int expectedQuarterTurns = _270DegreesClockwise; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - final int clockwiseQuarterTurns = rotatedBox.quarterTurns + 4; - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(clockwiseQuarterTurns, expectedQuarterTurns, - reason: getExpectedRotationTestFailureReason( - expectedQuarterTurns, rotatedBox.quarterTurns)); - }); - - testWidgets( - 'camera is back facing, then the preview Texture is rotated 90 degrees clockwise', - (WidgetTester tester) async { - // Set up test front camera with sensor orientation degrees 90. - final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); - final MockProcessCameraProvider mockProcessCameraProvider = - setUpMockCameraSelectorAndMockProcessCameraProviderForSelectingTestCamera( - mockCameraSelector: mockBackCameraSelector, - sensorRotationDegrees: testSensorOrientation); - - // Set up front camera selection and initial device orientation as landscape right. - final MockCameraSelector Function(int cameraSelectorLensDirection) - proxyCreateCameraSelectorForFrontCamera = - createCameraSelectorForBackCamera(mockBackCameraSelector); - camera.proxy = getProxyForCreatingTestCamera( - mockProcessCameraProvider: mockProcessCameraProvider, - createCameraSelector: proxyCreateCameraSelectorForFrontCamera, - handlesCropAndRotation: false, - getUiOrientation: () => - Future.value(testInitialDeviceOrientation)); - - // Get and create test camera. - final List availableCameras = - await camera.availableCameras(); - expect(availableCameras.length, 1); - await camera.createCameraWithSettings( - availableCameras.first, testMediaSettings); - - // Put camera preview in widget tree. - await tester.pumpWidget(camera.buildPreview(cameraId)); - - // Verify Texture is rotated by ((90 - 90 * -1 + 360) % 360) - 90 = 90 degrees clockwise. - const int expectedQuarterTurns = _90DegreesClockwise; - final RotatedBox rotatedBox = - tester.widget(find.byType(RotatedBox)); - expect(rotatedBox.child, isA()); - expect((rotatedBox.child! as Texture).textureId, cameraId); - expect(rotatedBox.quarterTurns, expectedQuarterTurns, - reason: getExpectedRotationTestFailureReason( - expectedQuarterTurns, rotatedBox.quarterTurns)); - }); - }); - }); -} diff --git a/packages/camera/camera_android_camerax/test/preview_test.dart b/packages/camera/camera_android_camerax/test/preview_test.dart index 4800b045e2d..b3cbc257b0c 100644 --- a/packages/camera/camera_android_camerax/test/preview_test.dart +++ b/packages/camera/camera_android_camerax/test/preview_test.dart @@ -40,7 +40,7 @@ void main() { verifyNever(mockApi.create(argThat(isA()), argThat(isA()), argThat(isA()))); - }, skip: 'Flaky test: https://github.com/flutter/flutter/issues/164132'); + }); test('create calls create on the Java side', () async { final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); @@ -170,19 +170,5 @@ void main() { verify(mockApi.getResolutionInfo(instanceManager.getIdentifier(preview))); }); - - test( - 'surfaceProducerHandlesCropAndRotation makes call to check if Android surface producer automatically corrects camera preview rotation', - () async { - final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); - TestPreviewHostApi.setup(mockApi); - final Preview preview = Preview.detached(); - - when(mockApi.surfaceProducerHandlesCropAndRotation()).thenReturn(true); - - expect(await preview.surfaceProducerHandlesCropAndRotation(), true); - - verify(mockApi.surfaceProducerHandlesCropAndRotation()); - }); }); } diff --git a/packages/camera/camera_android_camerax/test/preview_test.mocks.dart b/packages/camera/camera_android_camerax/test/preview_test.mocks.dart index d8b7a21e44b..8976a31245d 100644 --- a/packages/camera/camera_android_camerax/test/preview_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/preview_test.mocks.dart @@ -127,15 +127,6 @@ class MockTestPreviewHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); - - @override - bool surfaceProducerHandlesCropAndRotation() => (super.noSuchMethod( - Invocation.method( - #surfaceProducerHandlesCropAndRotation, - [], - ), - returnValue: false, - ) as bool); } /// A class which mocks [ResolutionSelector]. diff --git a/packages/camera/camera_android_camerax/test/recorder_test.dart b/packages/camera/camera_android_camerax/test/recorder_test.dart index 416ba0f7a47..c7b189f6130 100644 --- a/packages/camera/camera_android_camerax/test/recorder_test.dart +++ b/packages/camera/camera_android_camerax/test/recorder_test.dart @@ -43,7 +43,7 @@ void main() { verifyNever(mockApi.create(argThat(isA()), argThat(isA()), argThat(isA()), argThat(isA()))); - }, skip: 'Flaky test: https://github.com/flutter/flutter/issues/164132'); + }); test('create does call create on the Java side', () async { final MockTestRecorderHostApi mockApi = MockTestRecorderHostApi(); diff --git a/packages/camera/camera_android_camerax/test/system_services_test.dart b/packages/camera/camera_android_camerax/test/system_services_test.dart index 030f9aee5b2..d1725515e2f 100644 --- a/packages/camera/camera_android_camerax/test/system_services_test.dart +++ b/packages/camera/camera_android_camerax/test/system_services_test.dart @@ -85,4 +85,16 @@ void main() { verify(mockApi.getTempFilePath(testPrefix, testSuffix)); }); }); + + test('isPreviewPreTransformed returns expected answer', () async { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + const bool isPreviewPreTransformed = true; + + when(mockApi.isPreviewPreTransformed()).thenReturn(isPreviewPreTransformed); + + expect(await SystemServices.isPreviewPreTransformed(), isTrue); + verify(mockApi.isPreviewPreTransformed()); + }); } diff --git a/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart b/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart index ec97625adb9..9abb64c39bb 100644 --- a/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart @@ -87,4 +87,13 @@ class MockTestSystemServicesHostApi extends _i1.Mock ), ), ) as String); + + @override + bool isPreviewPreTransformed() => (super.noSuchMethod( + Invocation.method( + #isPreviewPreTransformed, + [], + ), + returnValue: false, + ) as bool); } diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart index 1007d975ebc..32235b878f3 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart @@ -510,6 +510,8 @@ abstract class TestSystemServicesHostApi { String getTempFilePath(String prefix, String suffix); + bool isPreviewPreTransformed(); + static void setup(TestSystemServicesHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -561,6 +563,24 @@ abstract class TestSystemServicesHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.isPreviewPreTransformed', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + // ignore message + final bool output = api.isPreviewPreTransformed(); + return [output]; + }); + } + } } } @@ -702,8 +722,6 @@ abstract class TestPreviewHostApi { void setTargetRotation(int identifier, int rotation); - bool surfaceProducerHandlesCropAndRotation(); - static void setup(TestPreviewHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -817,24 +835,6 @@ abstract class TestPreviewHostApi { }); } } - { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.PreviewHostApi.surfaceProducerHandlesCropAndRotation', - codec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, - (Object? message) async { - // ignore message - final bool output = api.surfaceProducerHandlesCropAndRotation(); - return [output]; - }); - } - } } } diff --git a/packages/file_selector/file_selector_android/android/build.gradle b/packages/file_selector/file_selector_android/android/build.gradle index d4c982bca14..7fd42103b6b 100644 --- a/packages/file_selector/file_selector_android/android/build.gradle +++ b/packages/file_selector/file_selector_android/android/build.gradle @@ -38,7 +38,7 @@ android { implementation 'androidx.annotation:annotation:1.9.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.1.0' - testImplementation 'androidx.test:core:1.4.0' + testImplementation 'androidx.test:core:1.3.0' testImplementation "org.robolectric:robolectric:4.12.1" } diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/MainActivity.java b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/MainActivity.java index dbf585b5984..1726aecbedd 100644 --- a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/MainActivity.java +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/MainActivity.java @@ -6,6 +6,7 @@ import android.util.Log; import androidx.lifecycle.Lifecycle; +import dev.flutter.plugins.integration_test.IntegrationTestPlugin; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.plugins.FlutterPlugin; @@ -18,8 +19,8 @@ public class MainActivity extends FlutterActivity { @Override public void configureFlutterEngine(FlutterEngine flutterEngine) { - super.configureFlutterEngine(flutterEngine); flutterEngine.getPlugins().add(new TestPlugin()); + flutterEngine.getPlugins().add(new IntegrationTestPlugin()); } private static class TestPlugin implements FlutterPlugin, ActivityAware { diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 19a6cdf7061..97d8dfe35c3 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,7 +1,3 @@ -## 14.8.1 - -- Secured canPop method for the lack of matches in routerDelegate's configuration. - ## 14.8.0 - Adds `preload` parameter to `StatefulShellBranchData.$branch`. diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart index aa95fdb39eb..3b90297be28 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -81,9 +81,6 @@ class GoRouterDelegate extends RouterDelegate if (navigatorKey.currentState?.canPop() ?? false) { return true; } - if (currentConfiguration.matches.isEmpty) { - return false; - } RouteMatchBase walker = currentConfiguration.matches.last; while (walker is ShellRouteMatch) { if (walker.navigatorKey.currentState?.canPop() ?? false) { diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index 86b0e9643fa..636d2637eb9 100644 --- a/packages/go_router/pubspec.yaml +++ b/packages/go_router/pubspec.yaml @@ -1,7 +1,7 @@ name: go_router description: A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more -version: 14.8.1 +version: 14.8.0 repository: https://github.com/flutter/packages/tree/main/packages/go_router issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22 diff --git a/packages/go_router/test/delegate_test.dart b/packages/go_router/test/delegate_test.dart index ad628610fd1..75db026fca6 100644 --- a/packages/go_router/test/delegate_test.dart +++ b/packages/go_router/test/delegate_test.dart @@ -317,20 +317,6 @@ void main() { expect(goRouter.routerDelegate.canPop(), true); }, ); - testWidgets( - 'It should return false if there are no matches in the stack', - (WidgetTester tester) async { - final GoRouter goRouter = GoRouter( - initialLocation: '/', - routes: [], - ); - addTearDown(goRouter.dispose); - await tester.pumpWidget(MaterialApp.router(routerConfig: goRouter)); - await tester.pumpAndSettle(); - expect(goRouter.routerDelegate.currentConfiguration.matches.length, 0); - expect(goRouter.routerDelegate.canPop(), false); - }, - ); }); group('pushReplacement', () { diff --git a/packages/go_router_builder/example/lib/all_types.g.dart b/packages/go_router_builder/example/lib/all_types.g.dart index a30f03f62c6..581814d47c2 100644 --- a/packages/go_router_builder/example/lib/all_types.g.dart +++ b/packages/go_router_builder/example/lib/all_types.g.dart @@ -716,7 +716,7 @@ bool _$boolConverter(String value) { } extension on Map { - T? _$fromName(String value) => + T? _$fromName(String? value) => entries.where((element) => element.value == value).firstOrNull?.key; } diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart index e489f48ad7d..ab4cbdb3540 100644 --- a/packages/go_router_builder/example/lib/main.g.dart +++ b/packages/go_router_builder/example/lib/main.g.dart @@ -147,7 +147,7 @@ extension $FamilyCountRouteExtension on FamilyCountRoute { } extension on Map { - T? _$fromName(String value) => + T? _$fromName(String? value) => entries.where((element) => element.value == value).firstOrNull?.key; } diff --git a/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.g.dart b/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.g.dart index 11aa3efae3c..f86f360772d 100644 --- a/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.g.dart +++ b/packages/go_router_builder/example/lib/stateful_shell_route_initial_location_example.g.dart @@ -111,6 +111,6 @@ extension $OrdersRouteDataExtension on OrdersRouteData { } extension on Map { - T? _$fromName(String value) => + T? _$fromName(String? value) => entries.where((element) => element.value == value).firstOrNull?.key; } diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index d272335db28..fe60df748c3 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -775,7 +775,7 @@ bool $boolConverterHelperName(String value) { const String _enumConverterHelper = ''' extension on Map { - T? $enumExtensionHelperName(String value) => + T? $enumExtensionHelperName(String? value) => entries.where((element) => element.value == value).firstOrNull?.key; }'''; diff --git a/packages/go_router_builder/test_inputs/enum_parameter.dart.expect b/packages/go_router_builder/test_inputs/enum_parameter.dart.expect index 97c63224b35..42d27809f85 100644 --- a/packages/go_router_builder/test_inputs/enum_parameter.dart.expect +++ b/packages/go_router_builder/test_inputs/enum_parameter.dart.expect @@ -29,6 +29,6 @@ const _$EnumTestEnumMap = { }; extension on Map { - T? _$fromName(String value) => + T? _$fromName(String? value) => entries.where((element) => element.value == value).firstOrNull?.key; } diff --git a/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect b/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect index 8d20676242f..88a9f97dc27 100644 --- a/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect +++ b/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect @@ -38,6 +38,6 @@ const _$EnumOnlyUsedInIterableEnumMap = { }; extension on Map { - T? _$fromName(String value) => + T? _$fromName(String? value) => entries.where((element) => element.value == value).firstOrNull?.key; } diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/app/build.gradle b/packages/google_maps_flutter/google_maps_flutter/example/android/app/build.gradle index 1151d176bc8..f19933ce0f1 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/android/app/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter/example/android/app/build.gradle @@ -57,7 +57,7 @@ android { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - api 'androidx.test:core:1.4.0' + api 'androidx.test:core:1.2.0' testImplementation 'com.google.android.gms:play-services-maps:17.0.0' } namespace 'io.flutter.plugins.googlemapsexample' diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle index d9e1055e225..a0f46118f4f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle @@ -44,7 +44,7 @@ android { androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' - testImplementation 'androidx.test:core:1.4.0' + testImplementation 'androidx.test:core:1.2.0' testImplementation "org.robolectric:robolectric:4.10.3" } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle index 89933cddf6a..0443d5854e5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle @@ -58,7 +58,7 @@ android { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - api 'androidx.test:core:1.4.0' + api 'androidx.test:core:1.2.0' testImplementation 'com.google.android.gms:play-services-maps:17.0.0' testImplementation 'com.google.maps.android:android-maps-utils:3.6.0' } diff --git a/packages/google_sign_in/google_sign_in/example/android/app/build.gradle b/packages/google_sign_in/google_sign_in/example/android/app/build.gradle index 1a30e11d0ad..55b8f8a7ea2 100644 --- a/packages/google_sign_in/google_sign_in/example/android/app/build.gradle +++ b/packages/google_sign_in/google_sign_in/example/android/app/build.gradle @@ -61,5 +61,5 @@ dependencies { testImplementation'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - api 'androidx.test:core:1.4.0' + api 'androidx.test:core:1.2.0' } diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index 9f52c627db3..ee1a1e0b61d 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -34,6 +34,8 @@ dev_dependencies: flutter_test: sdk: flutter http: ">=0.13.0 <2.0.0" + integration_test: + sdk: flutter mockito: ^5.4.4 topics: diff --git a/packages/google_sign_in/google_sign_in_android/example/android/app/build.gradle b/packages/google_sign_in/google_sign_in_android/example/android/app/build.gradle index 1a30e11d0ad..55b8f8a7ea2 100644 --- a/packages/google_sign_in/google_sign_in_android/example/android/app/build.gradle +++ b/packages/google_sign_in/google_sign_in_android/example/android/app/build.gradle @@ -61,5 +61,5 @@ dependencies { testImplementation'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - api 'androidx.test:core:1.4.0' + api 'androidx.test:core:1.2.0' } diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml index 2d276a00cc8..24e1f19973c 100644 --- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml @@ -26,6 +26,8 @@ dev_dependencies: build_runner: ^2.3.0 flutter_test: sdk: flutter + integration_test: + sdk: flutter mockito: ^5.4.4 pigeon: ^24.2.0 diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml index 9731d5cffb2..9c24833e595 100644 --- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml @@ -30,6 +30,8 @@ dev_dependencies: build_runner: ^2.4.6 flutter_test: sdk: flutter + integration_test: + sdk: flutter mockito: ^5.4.4 pigeon: ^22.4.2 diff --git a/packages/image_picker/image_picker/example/android/app/build.gradle b/packages/image_picker/image_picker/example/android/app/build.gradle index 72a711f6258..2dbef627b78 100755 --- a/packages/image_picker/image_picker/example/android/app/build.gradle +++ b/packages/image_picker/image_picker/example/android/app/build.gradle @@ -61,5 +61,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - api 'androidx.test:core:1.4.0' + api 'androidx.test:core:1.2.0' } diff --git a/packages/in_app_purchase/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/pubspec.yaml index b9cdb234c14..1c7e611ad2f 100644 --- a/packages/in_app_purchase/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/pubspec.yaml @@ -28,6 +28,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter plugin_platform_interface: ^2.1.7 test: ^1.16.0 diff --git a/packages/interactive_media_ads/android/build.gradle b/packages/interactive_media_ads/android/build.gradle index 4394306cd2f..05e3ff57d70 100644 --- a/packages/interactive_media_ads/android/build.gradle +++ b/packages/interactive_media_ads/android/build.gradle @@ -54,7 +54,7 @@ android { testImplementation 'org.jetbrains.kotlin:kotlin-test' testImplementation "org.mockito.kotlin:mockito-kotlin:5.4.0" testImplementation 'org.mockito:mockito-inline:5.1.0' - testImplementation 'androidx.test:core:1.4.0' + testImplementation 'androidx.test:core:1.3.0' } lintOptions { diff --git a/packages/local_auth/local_auth/example/android/app/build.gradle b/packages/local_auth/local_auth/example/android/app/build.gradle index 8831037f187..3a4d94f4146 100644 --- a/packages/local_auth/local_auth/example/android/app/build.gradle +++ b/packages/local_auth/local_auth/example/android/app/build.gradle @@ -30,7 +30,7 @@ android { defaultConfig { applicationId "io.flutter.plugins.localauthexample" minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion + targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index dc495efd488..e8156067086 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -32,6 +32,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter mockito: ^5.4.4 plugin_platform_interface: ^2.1.7 diff --git a/packages/path_provider/path_provider/pubspec.yaml b/packages/path_provider/path_provider/pubspec.yaml index f8379a62852..c972341e43b 100644 --- a/packages/path_provider/path_provider/pubspec.yaml +++ b/packages/path_provider/path_provider/pubspec.yaml @@ -34,6 +34,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter plugin_platform_interface: ^2.1.7 test: ^1.16.0 diff --git a/packages/path_provider/path_provider_android/pubspec.yaml b/packages/path_provider/path_provider_android/pubspec.yaml index 578adb88bf9..9674e6a1703 100644 --- a/packages/path_provider/path_provider_android/pubspec.yaml +++ b/packages/path_provider/path_provider_android/pubspec.yaml @@ -25,6 +25,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter pigeon: ^22.4.2 test: ^1.16.0 diff --git a/packages/pigeon/tool/shared/test_runner.dart b/packages/pigeon/tool/shared/test_runner.dart index 8fa487f42ef..cdb769fc029 100644 --- a/packages/pigeon/tool/shared/test_runner.dart +++ b/packages/pigeon/tool/shared/test_runner.dart @@ -22,46 +22,41 @@ Future runTests( }) async { final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath())); if (runGeneration) { - await _runGenerate(baseDir, ciMode: ciMode); + await _runGenerate(baseDir); } if (runFormat) { - await _runFormat(baseDir, ciMode: ciMode); + await _runFormat(baseDir); } await _runTests(testsToRun, ciMode: ciMode); if (includeOverflow) { - await _runGenerate(baseDir, ciMode: ciMode, includeOverflow: true); + await _runGenerate(baseDir, includeOverflow: true); // TODO(tarrinneal): Remove linux filter once overflow class is added to gobject generator. // https://github.com/flutter/flutter/issues/152916 - await _runTests( - testsToRun - .where((String test) => - test.contains('integration') && !test.contains('linux')) - .toList(), - ciMode: ciMode); + await _runTests(testsToRun + .where((String test) => + test.contains('integration') && !test.contains('linux')) + .toList()); if (!ciMode) { - await _runGenerate(baseDir, ciMode: ciMode); + await _runGenerate(baseDir); } if (!ciMode && (runFormat || !runGeneration)) { - await _runFormat(baseDir, ciMode: ciMode); + await _runFormat(baseDir); } } } // Pre-generate the necessary common output files. -Future _runGenerate( - String baseDir, { - required bool ciMode, - bool includeOverflow = false, -}) async { +Future _runGenerate(String baseDir, + {bool includeOverflow = false}) async { // TODO(stuartmorgan): Consider making this conditional on the specific // tests being run, as not all of them need these files. - _printHeading('Generating platform_test/ output', ciMode: ciMode); + print('# Generating platform_test/ output...'); final int generateExitCode = await generateTestPigeons( baseDir: baseDir, includeOverflow: includeOverflow, @@ -73,8 +68,8 @@ Future _runGenerate( } } -Future _runFormat(String baseDir, {required bool ciMode}) async { - _printHeading('Formatting generated output', ciMode: ciMode); +Future _runFormat(String baseDir) async { + print('Formatting generated output...'); final int formatExitCode = await formatAllFiles(repositoryRoot: p.dirname(p.dirname(baseDir))); if (formatExitCode != 0) { @@ -85,12 +80,13 @@ Future _runFormat(String baseDir, {required bool ciMode}) async { Future _runTests( List testsToRun, { - required bool ciMode, + bool ciMode = true, }) async { for (final String test in testsToRun) { final TestInfo? info = testSuites[test]; if (info != null) { - _printHeading('Running $test', ciMode: ciMode); + print('##############################'); + print('# Running $test'); final int testCode = await info.function(ciMode: ciMode); if (testCode != 0) { print('# Failed, exit code: $testCode'); @@ -104,13 +100,3 @@ Future _runTests( } } } - -void _printHeading(String heading, {required bool ciMode}) { - String timestamp = ''; - if (ciMode) { - final DateTime now = DateTime.now(); - timestamp = ' [start time ${now.hour}:${now.minute}:${now.second}]'; - } - print('##############################'); - print('# $heading$timestamp'); -} diff --git a/packages/quick_actions/quick_actions/example/android/app/build.gradle b/packages/quick_actions/quick_actions/example/android/app/build.gradle index 4fda6d0de9b..e069a067615 100644 --- a/packages/quick_actions/quick_actions/example/android/app/build.gradle +++ b/packages/quick_actions/quick_actions/example/android/app/build.gradle @@ -54,5 +54,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - api 'androidx.test:core:1.4.0' + api 'androidx.test:core:1.2.0' } diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml index 4ea40d1d3fc..a8e9f4e1c24 100644 --- a/packages/quick_actions/quick_actions/pubspec.yaml +++ b/packages/quick_actions/quick_actions/pubspec.yaml @@ -27,6 +27,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter mockito: ^5.4.4 plugin_platform_interface: ^2.1.7 diff --git a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle index 3da90333a77..bc68696eafb 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle +++ b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle @@ -22,7 +22,7 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -def androidXTestVersion = '1.4.0' +def androidXTestVersion = '1.2.0' android { namespace 'io.flutter.plugins.quickactionsexample' @@ -54,6 +54,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api "androidx.test:core:$androidXTestVersion" diff --git a/packages/quick_actions/quick_actions_android/pubspec.yaml b/packages/quick_actions/quick_actions_android/pubspec.yaml index 61b0c60b85a..c5e8b167892 100644 --- a/packages/quick_actions/quick_actions_android/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/pubspec.yaml @@ -25,6 +25,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter pigeon: ^24.0.0 plugin_platform_interface: ^2.1.7 diff --git a/packages/quick_actions/quick_actions_ios/pubspec.yaml b/packages/quick_actions/quick_actions_ios/pubspec.yaml index df9783ce1d8..973b5f9cdb5 100644 --- a/packages/quick_actions/quick_actions_ios/pubspec.yaml +++ b/packages/quick_actions/quick_actions_ios/pubspec.yaml @@ -24,6 +24,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter pigeon: ^20.0.1 plugin_platform_interface: ^2.1.7 diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index 3f535a86a02..6d6a20f725f 100644 --- a/packages/shared_preferences/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter path: ^1.9.0 topics: diff --git a/packages/url_launcher/url_launcher_android/android/build.gradle b/packages/url_launcher/url_launcher_android/android/build.gradle index 22c6af107bb..6c0f414ace6 100644 --- a/packages/url_launcher/url_launcher_android/android/build.gradle +++ b/packages/url_launcher/url_launcher_android/android/build.gradle @@ -66,6 +66,6 @@ dependencies { implementation 'androidx.browser:browser:1.8.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' - testImplementation 'androidx.test:core:1.4.0' + testImplementation 'androidx.test:core:1.0.0' testImplementation 'org.robolectric:robolectric:4.10.3' } diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 115414ab252..f923d118fca 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,7 +1,3 @@ -## 2.4.1 - -* Fixes a bug that triggers Links when they are not supposed to. - ## 2.4.0 * Enhances handling of out-of-order events. diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart index 678848b15a4..b8c11fa32da 100644 --- a/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart +++ b/packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart @@ -445,23 +445,25 @@ void main() { await followLinkCallback!(); await Future.delayed(const Duration(seconds: 1)); final html.Event event1 = _simulateClick(anchor); - await Future.delayed(const Duration(seconds: 1)); // The link shouldn't have been triggered. expect(pushedRouteNames, isEmpty); expect(testPlugin.launches, isEmpty); - expect(event1.defaultPrevented, isTrue); + expect(event1.defaultPrevented, isFalse); + + await Future.delayed(const Duration(seconds: 1)); // Signals with large delay (in reverse order). final html.Event event2 = _simulateClick(anchor); await Future.delayed(const Duration(seconds: 1)); await followLinkCallback!(); - await Future.delayed(const Duration(seconds: 1)); // The link shouldn't have been triggered. expect(pushedRouteNames, isEmpty); expect(testPlugin.launches, isEmpty); - expect(event2.defaultPrevented, isTrue); + expect(event2.defaultPrevented, isFalse); + + await Future.delayed(const Duration(seconds: 1)); // A small delay is okay. await followLinkCallback!(); @@ -471,7 +473,6 @@ void main() { // The link should've been triggered now. expect(pushedRouteNames, ['/foobar']); expect(testPlugin.launches, isEmpty); - // (default is prevented here because the link is internal) expect(event3.defaultPrevented, isTrue); }); @@ -971,109 +972,6 @@ void main() { expect(event.defaultPrevented, isTrue); }); - testWidgets('handles debounced clicks on semantic link', - (WidgetTester tester) async { - final Uri uri = Uri.parse('https://flutter.dev'); - FollowLink? followLinkCallback; - - await tester.pumpWidget(MaterialApp( - home: WebLinkDelegate( - semanticsIdentifier: 'test-link-71', - TestLinkInfo( - uri: uri, - target: LinkTarget.blank, - builder: (BuildContext context, FollowLink? followLink) { - followLinkCallback = followLink; - return GestureDetector( - onTap: () {}, - child: const Text('My Link'), - ); - }, - ), - ), - )); - // Platform view creation happens asynchronously. - await tester.pumpAndSettle(); - await tester.pump(); - - final html.Element semanticsHost = - html.document.createElement('flt-semantics-host'); - html.document.body!.append(semanticsHost); - final html.Element semanticsAnchor = html.document.createElement('a') - ..setAttribute('id', 'flt-semantic-node-99') - ..setAttribute('flt-semantics-identifier', 'test-link-71') - ..setAttribute('href', uri.toString()) - ..textContent = 'My Text Link'; - semanticsHost.append(semanticsAnchor); - - expect(pushedRouteNames, isEmpty); - expect(testPlugin.launches, isEmpty); - - // Receive the DOM click first. - final html.Event event = _simulateClick(semanticsAnchor); - // The browser will be prevented from navigating right at the end of the event loop because - // we are still not sure if the `followLink` will arrive or not. - await Future.delayed(Duration.zero); - expect(event.defaultPrevented, isTrue); - // After some delay, the engine's click debouncer sends the events to the framework and we get - // the `followLink` signal. - await Future.delayed(const Duration(milliseconds: 200)); - await followLinkCallback!(); - - expect(pushedRouteNames, isEmpty); - expect(testPlugin.launches, ['https://flutter.dev']); - }); - - // Regression test for: https://github.com/flutter/flutter/issues/162927 - testWidgets('prevents navigation with debounced clicks on semantic link', - (WidgetTester tester) async { - final Uri uri = Uri.parse('https://flutter.dev'); - - await tester.pumpWidget(MaterialApp( - home: WebLinkDelegate( - semanticsIdentifier: 'test-link-71', - TestLinkInfo( - uri: uri, - target: LinkTarget.blank, - builder: (BuildContext context, FollowLink? followLink) { - return GestureDetector( - onTap: () {}, - child: const Text('My Link'), - ); - }, - ), - ), - )); - // Platform view creation happens asynchronously. - await tester.pumpAndSettle(); - await tester.pump(); - - final html.Element semanticsHost = - html.document.createElement('flt-semantics-host'); - html.document.body!.append(semanticsHost); - final html.Element semanticsAnchor = html.document.createElement('a') - ..setAttribute('id', 'flt-semantic-node-99') - ..setAttribute('flt-semantics-identifier', 'test-link-71') - ..setAttribute('href', uri.toString()) - ..textContent = 'My Text Link'; - semanticsHost.append(semanticsAnchor); - - expect(pushedRouteNames, isEmpty); - expect(testPlugin.launches, isEmpty); - - // Receive the DOM click first. - final html.Event event = _simulateClick(semanticsAnchor); - // The browser will be prevented from navigating right at the end of the event loop because - // we are still not sure if the `followLink` will arrive or not. - await Future.delayed(Duration.zero); - expect(event.defaultPrevented, isTrue); - - // No navigation should occur because no `followLink` signal was received. - await Future.delayed(const Duration(seconds: 1)); - expect(pushedRouteNames, isEmpty); - expect(testPlugin.launches, isEmpty); - }); - // TODO(mdebbar): Remove this test after the engine PR [1] makes it to stable. // [1] https://github.com/flutter/engine/pull/52720 testWidgets('handles clicks on (old) semantic link with a button', diff --git a/packages/url_launcher/url_launcher_web/lib/src/link.dart b/packages/url_launcher/url_launcher_web/lib/src/link.dart index c24d47876ec..3540ac97f51 100644 --- a/packages/url_launcher/url_launcher_web/lib/src/link.dart +++ b/packages/url_launcher/url_launcher_web/lib/src/link.dart @@ -199,16 +199,6 @@ class LinkTriggerSignals { /// considered valid. If they don't, the signals are reset. final Duration staleTimeout; - /// Whether the browser can do the navigation itself. - /// - /// If the link is activated by a click event, then we know the browser can do the navigation by - /// itself. - /// - /// In some cases, we decide to prevent default on the click event, so [canBrowserNavigate] is - /// set to false to indicate that the browser is not able to perform the navigation anymore. In - /// such case, the navigation has to be performed programmatically (using `launchUrl`). - bool canBrowserNavigate = false; - bool get _hasAllSignals => _hasFollowLink && _hasDomEvent; int? get _viewId { @@ -242,7 +232,7 @@ class LinkTriggerSignals { void onFollowLink({required int viewId}) { _hasFollowLink = true; _viewIdFromFollowLink = viewId; - _scheduleReset(); + _didUpdate(); } /// Handles a [mouseEvent] signal from a specific [viewId]. @@ -264,26 +254,7 @@ class LinkTriggerSignals { _hasDomEvent = true; _viewIdFromDomEvent = viewId; _mouseEvent = mouseEvent; - _scheduleReset(); - - if (mouseEvent != null) { - canBrowserNavigate = true; - // We have to make a decision whether to allow the browser to navigate or not. The decision - // has to be made before the end of the event loop. - // - // If we don't hear back from the `followLink` signal by the end of the event loop, we should - // prevent the browser from navigating. If the `followLink` signal arrives later, we can - // navigate programmatically using `launchUrl`. - scheduleMicrotask(() { - if (!_hasDomEvent) { - // By the time the microtask runs, the link may have already been triggered. In that case, - // we want to do nothing here. - return; - } - canBrowserNavigate = false; - mouseEvent.preventDefault(); - }); - } + _didUpdate(); } bool _hasFollowLink = false; @@ -308,7 +279,7 @@ class LinkTriggerSignals { Timer? _resetTimer; - void _scheduleReset() { + void _didUpdate() { _resetTimer?.cancel(); _resetTimer = Timer(staleTimeout, reset); } @@ -325,7 +296,6 @@ class LinkTriggerSignals { _viewIdFromDomEvent = null; _mouseEvent = null; - canBrowserNavigate = false; } } @@ -544,7 +514,7 @@ class LinkViewController extends PlatformViewController { static void _triggerLink(int viewId, html.MouseEvent? mouseEvent) { final LinkViewController controller = _instancesByViewId[viewId]!; - if (_triggerSignals.canBrowserNavigate && _isModifierKey(mouseEvent)) { + if (mouseEvent != null && _isModifierKey(mouseEvent)) { // When the click is accompanied by a modifier key (e.g. cmd+click or // shift+click), we want to let the browser do its thing (e.g. open a new // tab or a new window). @@ -552,15 +522,15 @@ class LinkViewController extends PlatformViewController { } if (controller._isExternalLink) { - if (!_triggerSignals.canBrowserNavigate) { - // When the browser can't do the navigation, we have to launch the url manually. - UrlLauncherPlatform.instance.launchUrl( - controller._uri.toString(), - const LaunchOptions(), - ); + if (mouseEvent == null) { + // When external links are triggered by keyboard, they are not handled by + // the browser. So we have to launch the url manually. + UrlLauncherPlatform.instance + .launchUrl(controller._uri.toString(), const LaunchOptions()); } - // Otherwise, let the browser handle it, so we don't have to do anything. + // When triggerd by a mouse event, external links will be handled by the + // browser, so we don't have to do anything. return; } @@ -705,11 +675,7 @@ html.Element? _getClosestSemanticsLink(html.Element element) { return element.closest('a[id^="flt-semantic-node-"]'); } -bool _isModifierKey(html.Event? event) { - if (event == null) { - return false; - } - +bool _isModifierKey(html.Event event) { // This method accepts both KeyboardEvent and MouseEvent but there's no common // interface that contains the `ctrlKey`, `altKey`, `metaKey`, and `shiftKey` // properties. So we have to cast the event to either `KeyboardEvent` or diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index 0335a62f5ab..ee13f34f752 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_web description: Web platform implementation of url_launcher repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 2.4.1 +version: 2.4.0 environment: sdk: ^3.6.0 diff --git a/packages/video_player/video_player_android/android/build.gradle b/packages/video_player/video_player_android/android/build.gradle index dfc64bab624..9481aea9267 100644 --- a/packages/video_player/video_player_android/android/build.gradle +++ b/packages/video_player/video_player_android/android/build.gradle @@ -47,7 +47,7 @@ android { implementation "androidx.media3:media3-exoplayer-rtsp:${exoplayer_version}" implementation "androidx.media3:media3-exoplayer-smoothstreaming:${exoplayer_version}" testImplementation 'junit:junit:4.13.2' - testImplementation 'androidx.test:core:1.4.0' + testImplementation 'androidx.test:core:1.3.0' testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'org.robolectric:robolectric:4.10.3' testImplementation "androidx.media3:media3-test-utils:${exoplayer_version}" diff --git a/packages/webview_flutter/webview_flutter/example/android/app/build.gradle b/packages/webview_flutter/webview_flutter/example/android/app/build.gradle index 8ec364a9fa4..08f2adb2d06 100644 --- a/packages/webview_flutter/webview_flutter/example/android/app/build.gradle +++ b/packages/webview_flutter/webview_flutter/example/android/app/build.gradle @@ -57,5 +57,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - api 'androidx.test:core:1.4.0' + api 'androidx.test:core:1.2.0' } diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index 01a39dfd38f..b9df3fb0cdb 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -53,7 +53,7 @@ android { implementation 'androidx.webkit:webkit:1.12.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.1.0' - testImplementation 'androidx.test:core:1.4.0' + testImplementation 'androidx.test:core:1.3.0' } testOptions { diff --git a/script/tool/lib/src/firebase_test_lab_command.dart b/script/tool/lib/src/firebase_test_lab_command.dart index e430d3007af..5d2ab937c35 100644 --- a/script/tool/lib/src/firebase_test_lab_command.dart +++ b/script/tool/lib/src/firebase_test_lab_command.dart @@ -245,23 +245,22 @@ class FirebaseTestLabCommand extends PackageLoopingCommand { /// /// Returns true if either gradlew was already present, or the build succeeds. Future _ensureGradleWrapperExists(GradleProject project) async { - // Unconditionally re-run build with --debug --config-only, to ensure that - // the project is in a debug state even if it was previously configured. - print('Running flutter build apk...'); - final String experiment = getStringArg(kEnableExperiment); - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'build', - 'apk', - '--debug', - '--config-only', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - ], - workingDir: project.androidDirectory); - - if (exitCode != 0) { - return false; + if (!project.isConfigured()) { + print('Running flutter build apk...'); + final String experiment = getStringArg(kEnableExperiment); + final int exitCode = await processRunner.runAndStream( + flutterCommand, + [ + 'build', + 'apk', + '--config-only', + if (experiment.isNotEmpty) '--enable-experiment=$experiment', + ], + workingDir: project.androidDirectory); + + if (exitCode != 0) { + return false; + } } return true; } diff --git a/script/tool/test/create_all_packages_app_command_test.dart b/script/tool/test/create_all_packages_app_command_test.dart index 8b00139ff2b..b65371a1971 100644 --- a/script/tool/test/create_all_packages_app_command_test.dart +++ b/script/tool/test/create_all_packages_app_command_test.dart @@ -75,7 +75,7 @@ android { defaultConfig { applicationId "dev.flutter.packages.foo.example" minSdkVersion flutter.minSdkVersion - targetSdkVersion 35 + targetSdkVersion 32 } } diff --git a/script/tool/test/firebase_test_lab_command_test.dart b/script/tool/test/firebase_test_lab_command_test.dart index 2630402dfbd..067bf9cbaf5 100644 --- a/script/tool/test/firebase_test_lab_command_test.dart +++ b/script/tool/test/firebase_test_lab_command_test.dart @@ -164,14 +164,6 @@ public class MainActivityTest { expect( processRunner.recordedCalls, orderedEquals([ - ProcessCall( - 'flutter', - const ['build', 'apk', '--debug', '--config-only'], - plugin1 - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .path), ProcessCall( 'gcloud', 'auth activate-service-account --key-file=/path/to/key' @@ -193,14 +185,6 @@ public class MainActivityTest { 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://a_bucket --results-dir=plugins_android_test/plugin1/buildId/testRunId/example/0/ --device model=redfin,version=30 --device model=seoul,version=26' .split(' '), '/packages/plugin1/example'), - ProcessCall( - 'flutter', - const ['build', 'apk', '--debug', '--config-only'], - plugin2 - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .path), ProcessCall( '/packages/plugin2/example/android/gradlew', 'app:assembleAndroidTest -Pverbose=true'.split(' '), @@ -261,14 +245,6 @@ public class MainActivityTest { expect( processRunner.recordedCalls, orderedEquals([ - ProcessCall( - 'flutter', - const ['build', 'apk', '--debug', '--config-only'], - plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .path), ProcessCall( '/packages/plugin/example/android/gradlew', 'app:assembleAndroidTest -Pverbose=true'.split(' '), @@ -693,12 +669,8 @@ class MainActivityTest { orderedEquals([ ProcessCall( 'flutter', - 'build apk --debug --config-only'.split(' '), - plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .path, + 'build apk --config-only'.split(' '), + '/packages/plugin/example/android', ), ProcessCall( '/packages/plugin/example/android/gradlew', @@ -869,20 +841,6 @@ class MainActivityTest { expect( processRunner.recordedCalls, orderedEquals([ - ProcessCall( - 'flutter', - const [ - 'build', - 'apk', - '--debug', - '--config-only', - '--enable-experiment=exp1' - ], - plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .path), ProcessCall( '/packages/plugin/example/android/gradlew', 'app:assembleAndroidTest -Pverbose=true -Pextra-front-end-options=--enable-experiment%3Dexp1 -Pextra-gen-snapshot-options=--enable-experiment%3Dexp1'