Skip to content

Commit 7740990

Browse files
authored
[camerax] Re-land SurfaceProducer migration with fix for camera preview rotation (#6856)
Re-lands #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 (#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 flutter/flutter#149294.
1 parent b5d9074 commit 7740990

File tree

7 files changed

+95
-43
lines changed

7 files changed

+95
-43
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.6.8+1
2+
3+
* Re-lands support for Impeller.
4+
15
## 0.6.8
26

37
* Updates Guava version to 33.3.0.

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java

-7
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
package io.flutter.plugins.camerax;
66

77
import android.app.Activity;
8-
import android.graphics.SurfaceTexture;
98
import android.util.Size;
10-
import android.view.Surface;
119
import androidx.annotation.NonNull;
1210
import androidx.camera.core.CameraSelector;
1311
import androidx.camera.core.ImageAnalysis;
@@ -51,11 +49,6 @@ public class CameraXProxy {
5149
return new Preview.Builder();
5250
}
5351

54-
/** Creates a {@link Surface} instance from the specified {@link SurfaceTexture}. */
55-
public @NonNull Surface createSurface(@NonNull SurfaceTexture surfaceTexture) {
56-
return new Surface(surfaceTexture);
57-
}
58-
5952
/**
6053
* Creates an instance of the {@link SystemServicesFlutterApiImpl}.
6154
*

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java

+28-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
package io.flutter.plugins.camerax;
66

7-
import android.graphics.SurfaceTexture;
87
import android.util.Size;
98
import android.view.Surface;
109
import androidx.annotation.NonNull;
@@ -25,7 +24,7 @@ public class PreviewHostApiImpl implements PreviewHostApi {
2524
private final TextureRegistry textureRegistry;
2625

2726
@VisibleForTesting public @NonNull CameraXProxy cameraXProxy = new CameraXProxy();
28-
@VisibleForTesting public @Nullable TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture;
27+
@VisibleForTesting public @Nullable TextureRegistry.SurfaceProducer flutterSurfaceProducer;
2928

3029
public PreviewHostApiImpl(
3130
@NonNull BinaryMessenger binaryMessenger,
@@ -62,12 +61,11 @@ public void create(
6261
@Override
6362
public @NonNull Long setSurfaceProvider(@NonNull Long identifier) {
6463
Preview preview = getPreviewInstance(identifier);
65-
flutterSurfaceTexture = textureRegistry.createSurfaceTexture();
66-
SurfaceTexture surfaceTexture = flutterSurfaceTexture.surfaceTexture();
67-
Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(surfaceTexture);
64+
flutterSurfaceProducer = textureRegistry.createSurfaceProducer();
65+
Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(flutterSurfaceProducer);
6866
preview.setSurfaceProvider(surfaceProvider);
6967

70-
return flutterSurfaceTexture.id();
68+
return flutterSurfaceProducer.id();
7169
}
7270

7371
/**
@@ -76,13 +74,32 @@ public void create(
7674
*/
7775
@VisibleForTesting
7876
public @NonNull Preview.SurfaceProvider createSurfaceProvider(
79-
@NonNull SurfaceTexture surfaceTexture) {
77+
@NonNull TextureRegistry.SurfaceProducer surfaceProducer) {
8078
return new Preview.SurfaceProvider() {
8179
@Override
8280
public void onSurfaceRequested(@NonNull SurfaceRequest request) {
83-
surfaceTexture.setDefaultBufferSize(
81+
// Set callback for surfaceProducer to invalidate Surfaces that it produces when they
82+
// get destroyed.
83+
surfaceProducer.setCallback(
84+
new TextureRegistry.SurfaceProducer.Callback() {
85+
@Override
86+
public void onSurfaceCreated() {
87+
// Do nothing. The Preview.SurfaceProvider will handle this whenever a new
88+
// Surface is needed.
89+
}
90+
91+
@Override
92+
public void onSurfaceDestroyed() {
93+
// Invalidate the SurfaceRequest so that CameraX knows to to make a new request
94+
// for a surface.
95+
request.invalidate();
96+
}
97+
});
98+
99+
// Provide surface.
100+
surfaceProducer.setSize(
84101
request.getResolution().getWidth(), request.getResolution().getHeight());
85-
Surface flutterSurface = cameraXProxy.createSurface(surfaceTexture);
102+
Surface flutterSurface = surfaceProducer.getSurface();
86103
request.provideSurface(
87104
flutterSurface,
88105
Executors.newSingleThreadExecutor(),
@@ -133,8 +150,8 @@ String getProvideSurfaceErrorDescription(int resultCode) {
133150
*/
134151
@Override
135152
public void releaseFlutterSurfaceTexture() {
136-
if (flutterSurfaceTexture != null) {
137-
flutterSurfaceTexture.release();
153+
if (flutterSurfaceProducer != null) {
154+
flutterSurfaceProducer.release();
138155
}
139156
}
140157

packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java

+51-20
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
import static org.mockito.Mockito.reset;
1212
import static org.mockito.Mockito.spy;
1313
import static org.mockito.Mockito.verify;
14+
import static org.mockito.Mockito.verifyNoMoreInteractions;
1415
import static org.mockito.Mockito.when;
1516

16-
import android.graphics.SurfaceTexture;
1717
import android.util.Size;
1818
import android.view.Surface;
1919
import androidx.camera.core.Preview;
@@ -83,62 +83,93 @@ public void create_createsPreviewWithCorrectConfiguration() {
8383
}
8484

8585
@Test
86-
public void setSurfaceProviderTest_createsSurfaceProviderAndReturnsTextureEntryId() {
86+
public void setSurfaceProvider_createsSurfaceProviderAndReturnsTextureEntryId() {
8787
final PreviewHostApiImpl previewHostApi =
8888
spy(new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry));
89-
final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry =
90-
mock(TextureRegistry.SurfaceTextureEntry.class);
91-
final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class);
89+
final TextureRegistry.SurfaceProducer mockSurfaceProducer =
90+
mock(TextureRegistry.SurfaceProducer.class);
9291
final Long previewIdentifier = 5L;
93-
final Long surfaceTextureEntryId = 120L;
92+
final Long surfaceProducerEntryId = 120L;
9493

9594
previewHostApi.cameraXProxy = mockCameraXProxy;
9695
testInstanceManager.addDartCreatedInstance(mockPreview, previewIdentifier);
9796

98-
when(mockTextureRegistry.createSurfaceTexture()).thenReturn(mockSurfaceTextureEntry);
99-
when(mockSurfaceTextureEntry.surfaceTexture()).thenReturn(mockSurfaceTexture);
100-
when(mockSurfaceTextureEntry.id()).thenReturn(surfaceTextureEntryId);
97+
when(mockTextureRegistry.createSurfaceProducer()).thenReturn(mockSurfaceProducer);
98+
when(mockSurfaceProducer.id()).thenReturn(surfaceProducerEntryId);
10199

102100
final ArgumentCaptor<Preview.SurfaceProvider> surfaceProviderCaptor =
103101
ArgumentCaptor.forClass(Preview.SurfaceProvider.class);
104-
final ArgumentCaptor<Surface> surfaceCaptor = ArgumentCaptor.forClass(Surface.class);
105102

106103
// Test that surface provider was set and the surface texture ID was returned.
107-
assertEquals(previewHostApi.setSurfaceProvider(previewIdentifier), surfaceTextureEntryId);
104+
assertEquals(previewHostApi.setSurfaceProvider(previewIdentifier), surfaceProducerEntryId);
108105
verify(mockPreview).setSurfaceProvider(surfaceProviderCaptor.capture());
109-
verify(previewHostApi).createSurfaceProvider(mockSurfaceTexture);
106+
verify(previewHostApi).createSurfaceProvider(mockSurfaceProducer);
107+
}
108+
109+
@Test
110+
public void createSurfaceProducer_setsExpectedSurfaceProducerCallback() {
111+
final PreviewHostApiImpl previewHostApi =
112+
new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry);
113+
final TextureRegistry.SurfaceProducer mockSurfaceProducer =
114+
mock(TextureRegistry.SurfaceProducer.class);
115+
final SurfaceRequest mockSurfaceRequest = mock(SurfaceRequest.class);
116+
final ArgumentCaptor<TextureRegistry.SurfaceProducer.Callback> callbackCaptor =
117+
ArgumentCaptor.forClass(TextureRegistry.SurfaceProducer.Callback.class);
118+
119+
when(mockSurfaceRequest.getResolution()).thenReturn(new Size(5, 6));
120+
when(mockSurfaceProducer.getSurface()).thenReturn(mock(Surface.class));
121+
122+
Preview.SurfaceProvider previewSurfaceProvider =
123+
previewHostApi.createSurfaceProvider(mockSurfaceProducer);
124+
previewSurfaceProvider.onSurfaceRequested(mockSurfaceRequest);
125+
126+
verify(mockSurfaceProducer).setCallback(callbackCaptor.capture());
127+
128+
TextureRegistry.SurfaceProducer.Callback callback = callbackCaptor.getValue();
129+
130+
// Verify callback's onSurfaceDestroyed invalidates SurfaceRequest.
131+
callback.onSurfaceDestroyed();
132+
verify(mockSurfaceRequest).invalidate();
133+
134+
reset(mockSurfaceRequest);
135+
136+
// Verify callback's onSurfaceCreated does not interact with the SurfaceRequest.
137+
callback.onSurfaceCreated();
138+
verifyNoMoreInteractions(mockSurfaceRequest);
110139
}
111140

112141
@Test
113142
public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() {
114143
final PreviewHostApiImpl previewHostApi =
115144
new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry);
116-
final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class);
145+
final TextureRegistry.SurfaceProducer mockSurfaceProducer =
146+
mock(TextureRegistry.SurfaceProducer.class);
117147
final Surface mockSurface = mock(Surface.class);
118148
final SurfaceRequest mockSurfaceRequest = mock(SurfaceRequest.class);
119149
final SurfaceRequest.Result mockSurfaceRequestResult = mock(SurfaceRequest.Result.class);
120150
final SystemServicesFlutterApiImpl mockSystemServicesFlutterApi =
121151
mock(SystemServicesFlutterApiImpl.class);
122152
final int resolutionWidth = 200;
123153
final int resolutionHeight = 500;
154+
final Long surfaceProducerEntryId = 120L;
124155

125156
previewHostApi.cameraXProxy = mockCameraXProxy;
126-
when(mockCameraXProxy.createSurface(mockSurfaceTexture)).thenReturn(mockSurface);
127157
when(mockSurfaceRequest.getResolution())
128158
.thenReturn(new Size(resolutionWidth, resolutionHeight));
129159
when(mockCameraXProxy.createSystemServicesFlutterApiImpl(mockBinaryMessenger))
130160
.thenReturn(mockSystemServicesFlutterApi);
161+
when(mockSurfaceProducer.getSurface()).thenReturn(mockSurface);
131162

132163
final ArgumentCaptor<Surface> surfaceCaptor = ArgumentCaptor.forClass(Surface.class);
133164
@SuppressWarnings("unchecked")
134165
final ArgumentCaptor<Consumer<SurfaceRequest.Result>> consumerCaptor =
135166
ArgumentCaptor.forClass(Consumer.class);
136167

137168
Preview.SurfaceProvider previewSurfaceProvider =
138-
previewHostApi.createSurfaceProvider(mockSurfaceTexture);
169+
previewHostApi.createSurfaceProvider(mockSurfaceProducer);
139170
previewSurfaceProvider.onSurfaceRequested(mockSurfaceRequest);
140171

141-
verify(mockSurfaceTexture).setDefaultBufferSize(resolutionWidth, resolutionHeight);
172+
verify(mockSurfaceProducer).setSize(resolutionWidth, resolutionHeight);
142173
verify(mockSurfaceRequest)
143174
.provideSurface(surfaceCaptor.capture(), any(Executor.class), consumerCaptor.capture());
144175

@@ -189,13 +220,13 @@ public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() {
189220
public void releaseFlutterSurfaceTexture_makesCallToReleaseFlutterSurfaceTexture() {
190221
final PreviewHostApiImpl previewHostApi =
191222
new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry);
192-
final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry =
193-
mock(TextureRegistry.SurfaceTextureEntry.class);
223+
final TextureRegistry.SurfaceProducer mockSurfaceProducer =
224+
mock(TextureRegistry.SurfaceProducer.class);
194225

195-
previewHostApi.flutterSurfaceTexture = mockSurfaceTextureEntry;
226+
previewHostApi.flutterSurfaceProducer = mockSurfaceProducer;
196227

197228
previewHostApi.releaseFlutterSurfaceTexture();
198-
verify(mockSurfaceTextureEntry).release();
229+
verify(mockSurfaceProducer).release();
199230
}
200231

201232
@Test

packages/camera/camera_android_camerax/lib/src/system_services.dart

+3-4
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,9 @@ class SystemServices {
5959
/// be corrected by the plugin.
6060
static Future<bool> isPreviewPreTransformed(
6161
{BinaryMessenger? binaryMessenger}) {
62-
// TODO(camsim99): Make call to Java host to determine true value when
63-
// Impeller support is re-landed.
64-
// https://github.com/flutter/flutter/issues/149294
65-
return Future<bool>.value(true);
62+
final SystemServicesHostApi api =
63+
SystemServicesHostApi(binaryMessenger: binaryMessenger);
64+
return api.isPreviewPreTransformed();
6665
}
6766
}
6867

packages/camera/camera_android_camerax/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_android_camerax
22
description: Android implementation of the camera plugin using the CameraX library.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.6.8
5+
version: 0.6.8+1
66

77
environment:
88
sdk: ^3.4.0

packages/camera/camera_android_camerax/test/system_services_test.dart

+8
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ void main() {
8787
});
8888

8989
test('isPreviewPreTransformed returns expected answer', () async {
90+
final MockTestSystemServicesHostApi mockApi =
91+
MockTestSystemServicesHostApi();
92+
TestSystemServicesHostApi.setup(mockApi);
93+
const bool isPreviewPreTransformed = true;
94+
95+
when(mockApi.isPreviewPreTransformed()).thenReturn(isPreviewPreTransformed);
96+
9097
expect(await SystemServices.isPreviewPreTransformed(), isTrue);
98+
verify(mockApi.isPreviewPreTransformed());
9199
});
92100
}

0 commit comments

Comments
 (0)