From 7740990781f4417992224c8336a84a241d047691 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:38:16 -0700 Subject: [PATCH] [camerax] Re-land SurfaceProducer migration with fix for camera preview rotation (#6856) Re-lands https://github.com/flutter/packages/pull/6462 with the following change to `buildPreview`: #### Switches on a fix for correctly rotating the camera preview This fix is required for `Surface`s not backed by a `SurfaceTexture` because they don't contain the transformation information needed to correctly rotate the camera preview. In that case, we use the logic described in https://developer.android.com/media/camera/camera2/camera-preview#orientation_calculation. This logic was added in a previous PR (https://github.com/flutter/packages/pull/7044); this simply enables it for devices where `Surface`s are not backed by a `SurfaceTexture` (see [how this is determined](https://github.com/flutter/packages/blob/f118119cf830fe369b34a04ed63c9ed66c647985/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java#L107-L120)). Fixes https://github.com/flutter/flutter/issues/149294. --- .../camera_android_camerax/CHANGELOG.md | 4 ++ .../flutter/plugins/camerax/CameraXProxy.java | 7 -- .../plugins/camerax/PreviewHostApiImpl.java | 39 +++++++--- .../flutter/plugins/camerax/PreviewTest.java | 71 +++++++++++++------ .../lib/src/system_services.dart | 7 +- .../camera_android_camerax/pubspec.yaml | 2 +- .../test/system_services_test.dart | 8 +++ 7 files changed, 95 insertions(+), 43 deletions(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index ec56ffe52e1a..8077f54b19c6 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.8+1 + +* Re-lands support for Impeller. + ## 0.6.8 * Updates Guava version to 33.3.0. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java index af7fdc36a721..e749940200a6 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java @@ -5,9 +5,7 @@ package io.flutter.plugins.camerax; import android.app.Activity; -import android.graphics.SurfaceTexture; import android.util.Size; -import android.view.Surface; import androidx.annotation.NonNull; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageAnalysis; @@ -51,11 +49,6 @@ public class CameraXProxy { return new Preview.Builder(); } - /** Creates a {@link Surface} instance from the specified {@link SurfaceTexture}. */ - public @NonNull Surface createSurface(@NonNull SurfaceTexture surfaceTexture) { - return new Surface(surfaceTexture); - } - /** * Creates an instance of the {@link SystemServicesFlutterApiImpl}. * 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 7b1ba1214cdb..81d47936bcaf 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 @@ -4,7 +4,6 @@ package io.flutter.plugins.camerax; -import android.graphics.SurfaceTexture; import android.util.Size; import android.view.Surface; import androidx.annotation.NonNull; @@ -25,7 +24,7 @@ public class PreviewHostApiImpl implements PreviewHostApi { private final TextureRegistry textureRegistry; @VisibleForTesting public @NonNull CameraXProxy cameraXProxy = new CameraXProxy(); - @VisibleForTesting public @Nullable TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture; + @VisibleForTesting public @Nullable TextureRegistry.SurfaceProducer flutterSurfaceProducer; public PreviewHostApiImpl( @NonNull BinaryMessenger binaryMessenger, @@ -62,12 +61,11 @@ public void create( @Override public @NonNull Long setSurfaceProvider(@NonNull Long identifier) { Preview preview = getPreviewInstance(identifier); - flutterSurfaceTexture = textureRegistry.createSurfaceTexture(); - SurfaceTexture surfaceTexture = flutterSurfaceTexture.surfaceTexture(); - Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(surfaceTexture); + flutterSurfaceProducer = textureRegistry.createSurfaceProducer(); + Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(flutterSurfaceProducer); preview.setSurfaceProvider(surfaceProvider); - return flutterSurfaceTexture.id(); + return flutterSurfaceProducer.id(); } /** @@ -76,13 +74,32 @@ public void create( */ @VisibleForTesting public @NonNull Preview.SurfaceProvider createSurfaceProvider( - @NonNull SurfaceTexture surfaceTexture) { + @NonNull TextureRegistry.SurfaceProducer surfaceProducer) { return new Preview.SurfaceProvider() { @Override public void onSurfaceRequested(@NonNull SurfaceRequest request) { - surfaceTexture.setDefaultBufferSize( + // Set callback for surfaceProducer to invalidate Surfaces that it produces when they + // get destroyed. + surfaceProducer.setCallback( + new TextureRegistry.SurfaceProducer.Callback() { + @Override + public void onSurfaceCreated() { + // Do nothing. The Preview.SurfaceProvider will handle this whenever a new + // Surface is needed. + } + + @Override + public void onSurfaceDestroyed() { + // Invalidate the SurfaceRequest so that CameraX knows to to make a new request + // for a surface. + request.invalidate(); + } + }); + + // Provide surface. + surfaceProducer.setSize( request.getResolution().getWidth(), request.getResolution().getHeight()); - Surface flutterSurface = cameraXProxy.createSurface(surfaceTexture); + Surface flutterSurface = surfaceProducer.getSurface(); request.provideSurface( flutterSurface, Executors.newSingleThreadExecutor(), @@ -133,8 +150,8 @@ String getProvideSurfaceErrorDescription(int resultCode) { */ @Override public void releaseFlutterSurfaceTexture() { - if (flutterSurfaceTexture != null) { - flutterSurfaceTexture.release(); + if (flutterSurfaceProducer != null) { + flutterSurfaceProducer.release(); } } 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 81b455d7a867..0b475b7a1dc6 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 @@ -11,9 +11,9 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import android.graphics.SurfaceTexture; import android.util.Size; import android.view.Surface; import androidx.camera.core.Preview; @@ -83,37 +83,67 @@ public void create_createsPreviewWithCorrectConfiguration() { } @Test - public void setSurfaceProviderTest_createsSurfaceProviderAndReturnsTextureEntryId() { + public void setSurfaceProvider_createsSurfaceProviderAndReturnsTextureEntryId() { final PreviewHostApiImpl previewHostApi = spy(new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry)); - final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry = - mock(TextureRegistry.SurfaceTextureEntry.class); - final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); + final TextureRegistry.SurfaceProducer mockSurfaceProducer = + mock(TextureRegistry.SurfaceProducer.class); final Long previewIdentifier = 5L; - final Long surfaceTextureEntryId = 120L; + final Long surfaceProducerEntryId = 120L; previewHostApi.cameraXProxy = mockCameraXProxy; testInstanceManager.addDartCreatedInstance(mockPreview, previewIdentifier); - when(mockTextureRegistry.createSurfaceTexture()).thenReturn(mockSurfaceTextureEntry); - when(mockSurfaceTextureEntry.surfaceTexture()).thenReturn(mockSurfaceTexture); - when(mockSurfaceTextureEntry.id()).thenReturn(surfaceTextureEntryId); + when(mockTextureRegistry.createSurfaceProducer()).thenReturn(mockSurfaceProducer); + when(mockSurfaceProducer.id()).thenReturn(surfaceProducerEntryId); final ArgumentCaptor surfaceProviderCaptor = ArgumentCaptor.forClass(Preview.SurfaceProvider.class); - final ArgumentCaptor surfaceCaptor = ArgumentCaptor.forClass(Surface.class); // Test that surface provider was set and the surface texture ID was returned. - assertEquals(previewHostApi.setSurfaceProvider(previewIdentifier), surfaceTextureEntryId); + assertEquals(previewHostApi.setSurfaceProvider(previewIdentifier), surfaceProducerEntryId); verify(mockPreview).setSurfaceProvider(surfaceProviderCaptor.capture()); - verify(previewHostApi).createSurfaceProvider(mockSurfaceTexture); + verify(previewHostApi).createSurfaceProvider(mockSurfaceProducer); + } + + @Test + public void createSurfaceProducer_setsExpectedSurfaceProducerCallback() { + final PreviewHostApiImpl previewHostApi = + new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); + final TextureRegistry.SurfaceProducer mockSurfaceProducer = + mock(TextureRegistry.SurfaceProducer.class); + final SurfaceRequest mockSurfaceRequest = mock(SurfaceRequest.class); + final ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(TextureRegistry.SurfaceProducer.Callback.class); + + when(mockSurfaceRequest.getResolution()).thenReturn(new Size(5, 6)); + when(mockSurfaceProducer.getSurface()).thenReturn(mock(Surface.class)); + + Preview.SurfaceProvider previewSurfaceProvider = + previewHostApi.createSurfaceProvider(mockSurfaceProducer); + previewSurfaceProvider.onSurfaceRequested(mockSurfaceRequest); + + verify(mockSurfaceProducer).setCallback(callbackCaptor.capture()); + + TextureRegistry.SurfaceProducer.Callback callback = callbackCaptor.getValue(); + + // Verify callback's onSurfaceDestroyed invalidates SurfaceRequest. + callback.onSurfaceDestroyed(); + verify(mockSurfaceRequest).invalidate(); + + reset(mockSurfaceRequest); + + // Verify callback's onSurfaceCreated does not interact with the SurfaceRequest. + callback.onSurfaceCreated(); + verifyNoMoreInteractions(mockSurfaceRequest); } @Test public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() { final PreviewHostApiImpl previewHostApi = new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); - final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); + final TextureRegistry.SurfaceProducer mockSurfaceProducer = + mock(TextureRegistry.SurfaceProducer.class); final Surface mockSurface = mock(Surface.class); final SurfaceRequest mockSurfaceRequest = mock(SurfaceRequest.class); final SurfaceRequest.Result mockSurfaceRequestResult = mock(SurfaceRequest.Result.class); @@ -121,13 +151,14 @@ public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() { mock(SystemServicesFlutterApiImpl.class); final int resolutionWidth = 200; final int resolutionHeight = 500; + final Long surfaceProducerEntryId = 120L; previewHostApi.cameraXProxy = mockCameraXProxy; - when(mockCameraXProxy.createSurface(mockSurfaceTexture)).thenReturn(mockSurface); when(mockSurfaceRequest.getResolution()) .thenReturn(new Size(resolutionWidth, resolutionHeight)); when(mockCameraXProxy.createSystemServicesFlutterApiImpl(mockBinaryMessenger)) .thenReturn(mockSystemServicesFlutterApi); + when(mockSurfaceProducer.getSurface()).thenReturn(mockSurface); final ArgumentCaptor surfaceCaptor = ArgumentCaptor.forClass(Surface.class); @SuppressWarnings("unchecked") @@ -135,10 +166,10 @@ public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() { ArgumentCaptor.forClass(Consumer.class); Preview.SurfaceProvider previewSurfaceProvider = - previewHostApi.createSurfaceProvider(mockSurfaceTexture); + previewHostApi.createSurfaceProvider(mockSurfaceProducer); previewSurfaceProvider.onSurfaceRequested(mockSurfaceRequest); - verify(mockSurfaceTexture).setDefaultBufferSize(resolutionWidth, resolutionHeight); + verify(mockSurfaceProducer).setSize(resolutionWidth, resolutionHeight); verify(mockSurfaceRequest) .provideSurface(surfaceCaptor.capture(), any(Executor.class), consumerCaptor.capture()); @@ -189,13 +220,13 @@ public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() { public void releaseFlutterSurfaceTexture_makesCallToReleaseFlutterSurfaceTexture() { final PreviewHostApiImpl previewHostApi = new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); - final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry = - mock(TextureRegistry.SurfaceTextureEntry.class); + final TextureRegistry.SurfaceProducer mockSurfaceProducer = + mock(TextureRegistry.SurfaceProducer.class); - previewHostApi.flutterSurfaceTexture = mockSurfaceTextureEntry; + previewHostApi.flutterSurfaceProducer = mockSurfaceProducer; previewHostApi.releaseFlutterSurfaceTexture(); - verify(mockSurfaceTextureEntry).release(); + verify(mockSurfaceProducer).release(); } @Test 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 a55981213266..5f59bd2f4c60 100644 --- a/packages/camera/camera_android_camerax/lib/src/system_services.dart +++ b/packages/camera/camera_android_camerax/lib/src/system_services.dart @@ -59,10 +59,9 @@ class SystemServices { /// be corrected by the plugin. static Future isPreviewPreTransformed( {BinaryMessenger? binaryMessenger}) { - // TODO(camsim99): Make call to Java host to determine true value when - // Impeller support is re-landed. - // https://github.com/flutter/flutter/issues/149294 - return Future.value(true); + final SystemServicesHostApi api = + SystemServicesHostApi(binaryMessenger: binaryMessenger); + return api.isPreviewPreTransformed(); } } diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 714c19da6813..0bce47098752 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.8 +version: 0.6.8+1 environment: sdk: ^3.4.0 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 1d5d00ee7169..d1725515e2f8 100644 --- a/packages/camera/camera_android_camerax/test/system_services_test.dart +++ b/packages/camera/camera_android_camerax/test/system_services_test.dart @@ -87,6 +87,14 @@ void main() { }); 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()); }); }