From fbb301ab1821013e2caf4f942583dba7ae495f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rulong=20Chen=EF=BC=88=E9=99=88=E6=B1=9D=E9=BE=99=EF=BC=89?= Date: Sat, 26 Feb 2022 23:00:26 +0800 Subject: [PATCH 1/2] Fix compatibility issues with SurfaceTexture on Android Q. --- .../engine/renderer/FlutterRenderer.java | 21 +++++- .../renderer/SurfaceTextureWrapper.java | 11 +++ .../plugin/platform/PlatformViewWrapper.java | 73 ++++++++++++++++--- .../platform/PlatformViewsController.java | 3 +- .../android/io/flutter/view/FlutterView.java | 22 +++++- .../io/flutter/view/TextureRegistry.java | 16 ++++ .../engine/renderer/FlutterRendererTest.java | 30 ++++++++ 7 files changed, 160 insertions(+), 16 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index d58e1b791870e..5e4b14ed098a8 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -121,10 +121,20 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur private final long id; @NonNull private final SurfaceTextureWrapper textureWrapper; private boolean released; + @Nullable private ImageFrameListener listener; + private final Runnable onFrameConsumed = + new Runnable() { + @Override + public void run() { + if (listener != null) { + listener.onFrameConsumed(); + } + } + }; SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture) { this.id = id; - this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); + this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture, onFrameConsumed); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // The callback relies on being executed on the UI thread (unsynchronised read of @@ -152,6 +162,10 @@ public void onFrameAvailable(@NonNull SurfaceTexture texture) { // mNativeView==null. return; } + + if (listener != null) { + listener.onFrameAvailable(); + } markTextureFrameAvailable(id); } }; @@ -195,6 +209,11 @@ protected void finalize() throws Throwable { super.finalize(); } } + + @Override + public void setImageFrameListener(@Nullable ImageFrameListener listener) { + this.listener = listener; + } } static final class SurfaceTextureFinalizerRunnable implements Runnable { diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java index 379fd3c558fc1..c362ee9e9e35e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java @@ -7,6 +7,7 @@ import android.graphics.SurfaceTexture; import androidx.annotation.Keep; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * A wrapper for a SurfaceTexture that tracks whether the texture has been released. @@ -20,10 +21,17 @@ public class SurfaceTextureWrapper { private SurfaceTexture surfaceTexture; private boolean released; private boolean attached; + private Runnable onFrameConsumed; public SurfaceTextureWrapper(@NonNull SurfaceTexture surfaceTexture) { + this(surfaceTexture, null); + } + + public SurfaceTextureWrapper( + @NonNull SurfaceTexture surfaceTexture, @Nullable Runnable onFrameConsumed) { this.surfaceTexture = surfaceTexture; this.released = false; + this.onFrameConsumed = onFrameConsumed; } @NonNull @@ -37,6 +45,9 @@ public void updateTexImage() { synchronized (this) { if (!released) { surfaceTexture.updateTexImage(); + if (onFrameConsumed != null) { + onFrameConsumed.run(); + } } } } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index ba0205a7b76df..871a0a5f754a9 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -26,6 +26,8 @@ import io.flutter.Log; import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.util.ViewUtils; +import io.flutter.view.TextureRegistry; +import java.util.concurrent.atomic.AtomicLong; /** * Wraps a platform view to intercept gestures and project this view onto a {@link SurfaceTexture}. @@ -52,12 +54,42 @@ class PlatformViewWrapper extends FrameLayout { private AndroidTouchProcessor touchProcessor; @Nullable @VisibleForTesting ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener; + @Nullable private TextureRegistry.SurfaceTextureEntry textureEntry; + private final AtomicLong pendingFramesCount = new AtomicLong(0L); + + private final TextureRegistry.ImageFrameListener frameListener = + new TextureRegistry.ImageFrameListener() { + @Override + public void onFrameAvailable() {} + + @Override + public void onFrameConsumed() { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + pendingFramesCount.decrementAndGet(); + } + } + }; + + private boolean shouldDrawToSurfaceNow() { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + return pendingFramesCount.get() <= 0L; + } + return true; + } public PlatformViewWrapper(@NonNull Context context) { super(context); setWillNotDraw(false); } + public PlatformViewWrapper( + @NonNull Context context, @NonNull TextureRegistry.SurfaceTextureEntry textureEntry) { + this(context); + this.textureEntry = textureEntry; + textureEntry.setImageFrameListener(frameListener); + setTexture(textureEntry.surfaceTexture()); + } + /** * Sets the touch processor that allows to intercept gestures. * @@ -109,6 +141,10 @@ public void setTexture(@Nullable SurfaceTexture newTx) { } else { canvas.drawColor(Color.TRANSPARENT); } + + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + pendingFramesCount.incrementAndGet(); + } } finally { surface.unlockCanvasAndPost(canvas); } @@ -202,19 +238,32 @@ public void draw(Canvas canvas) { Log.e(TAG, "Invalid texture. The platform view cannot be displayed."); return; } - // Override the canvas that this subtree of views will use to draw. - final Canvas surfaceCanvas = surface.lockHardwareCanvas(); - try { - // Clear the current pixels in the canvas. - // This helps when a WebView renders an HTML document with transparent background. - if (Build.VERSION.SDK_INT >= 29) { - surfaceCanvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); - } else { - surfaceCanvas.drawColor(Color.TRANSPARENT); + // We've observed on Android Q that we have to wait for the consumer of {@link SurfaceTexture} + // to consume the last image before continuing to draw, otherwise subsequent calls to + // {@code dequeueBuffer} to request a free buffer from the {@link BufferQueue} will fail. + // See https://github.com/flutter/flutter/issues/98722 + if (!shouldDrawToSurfaceNow()) { + // If there are still frames that are not consumed, we will draw them next time. + invalidate(); + } else { + // Override the canvas that this subtree of views will use to draw. + final Canvas surfaceCanvas = surface.lockHardwareCanvas(); + try { + // Clear the current pixels in the canvas. + // This helps when a WebView renders an HTML document with transparent background. + if (Build.VERSION.SDK_INT >= 29) { + surfaceCanvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + } else { + surfaceCanvas.drawColor(Color.TRANSPARENT); + } + super.draw(surfaceCanvas); + + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + pendingFramesCount.incrementAndGet(); + } + } finally { + surface.unlockCanvasAndPost(surfaceCanvas); } - super.draw(surfaceCanvas); - } finally { - surface.unlockCanvasAndPost(surfaceCanvas); } } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 3d9e86d749557..c8326a387e777 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -188,10 +188,9 @@ public long createForTextureLayer( final PlatformView platformView = viewFactory.create(context, viewId, createParams); platformViews.put(viewId, platformView); - final PlatformViewWrapper wrapperView = new PlatformViewWrapper(context); final TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture(); - wrapperView.setTexture(textureEntry.surfaceTexture()); + final PlatformViewWrapper wrapperView = new PlatformViewWrapper(context, textureEntry); wrapperView.setTouchProcessor(androidTouchProcessor); final int physicalWidth = toPhysicalPixels(request.logicalWidth); diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index f47b8da5bde00..772277b0cc0fd 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -37,6 +37,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.UiThread; import io.flutter.Log; @@ -886,10 +887,20 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur private final long id; private final SurfaceTextureWrapper textureWrapper; private boolean released; + @Nullable private ImageFrameListener listener; + private final Runnable onFrameConsumed = + new Runnable() { + @Override + public void run() { + if (listener != null) { + listener.onFrameConsumed(); + } + } + }; SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture) { this.id = id; - this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); + this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture, onFrameConsumed); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // The callback relies on being executed on the UI thread (unsynchronised read of @@ -917,6 +928,10 @@ public void onFrameAvailable(SurfaceTexture texture) { // still be called by a stale reference after released==true and mNativeView==null. return; } + + if (listener != null) { + listener.onFrameAvailable(); + } mNativeView .getFlutterJNI() .markTextureFrameAvailable(SurfaceTextureRegistryEntry.this.id); @@ -955,5 +970,10 @@ public void release() { textureWrapper.release(); mNativeView.getFlutterJNI().unregisterTexture(id); } + + @Override + public void setImageFrameListener(@Nullable ImageFrameListener listener) { + this.listener = listener; + } } } diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index 1155c4854bf66..205799ffab516 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -6,6 +6,7 @@ import android.graphics.SurfaceTexture; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; // TODO(mattcarroll): re-evalute docs in this class and add nullability annotations. /** @@ -41,5 +42,20 @@ interface SurfaceTextureEntry { /** Deregisters and releases this SurfaceTexture. */ void release(); + + /** Set a listener that will be notified when a image frame state changes. */ + default void setImageFrameListener(@Nullable ImageFrameListener listener) {} + } + + /** Listener invoked when a new image frame becomes available and has been consumed. */ + interface ImageFrameListener { + /** This method will to be invoked when a new image frame becomes available. */ + void onFrameAvailable(); + + /** + * This method will to be invoked when the most recent image from the image stream has been + * consumed. + */ + void onFrameConsumed(); } } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index e38001a7231c2..0ce34da7296ad 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -18,8 +18,10 @@ import android.view.Surface; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.view.TextureRegistry; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -312,4 +314,32 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { }, stateCaptor.getValue()); } + + @Test + public void itNotifyImageFrameListener() { + // Setup the test. + FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI); + + AtomicInteger invocationCount = new AtomicInteger(0); + final TextureRegistry.ImageFrameListener listener = + new TextureRegistry.ImageFrameListener() { + @Override + public void onFrameAvailable() {} + + @Override + public void onFrameConsumed() { + invocationCount.incrementAndGet(); + } + }; + + FlutterRenderer.SurfaceTextureRegistryEntry entry = + (FlutterRenderer.SurfaceTextureRegistryEntry) flutterRenderer.createSurfaceTexture(); + entry.setImageFrameListener(listener); + + // Execute the behavior under test. + entry.textureWrapper().updateTexImage(); + + // Verify behavior under test. + assertEquals(1, invocationCount.get()); + } } From 4d3a60786c0b776217e4eef708ae7d58060802cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rulong=20Chen=EF=BC=88=E9=99=88=E6=B1=9D=E9=BE=99=EF=BC=89?= Date: Sat, 5 Mar 2022 17:27:37 +0800 Subject: [PATCH 2/2] refactoring. --- .../engine/renderer/FlutterRenderer.java | 8 ++--- .../renderer/SurfaceTextureWrapper.java | 8 +++++ .../plugin/platform/PlatformViewWrapper.java | 29 +++++++++---------- .../android/io/flutter/view/FlutterView.java | 21 +------------- .../io/flutter/view/TextureRegistry.java | 11 +++---- .../engine/renderer/FlutterRendererTest.java | 9 ++---- 6 files changed, 31 insertions(+), 55 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 5e4b14ed098a8..67c5e390185eb 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -121,7 +121,7 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur private final long id; @NonNull private final SurfaceTextureWrapper textureWrapper; private boolean released; - @Nullable private ImageFrameListener listener; + @Nullable private OnFrameConsumedListener listener; private final Runnable onFrameConsumed = new Runnable() { @Override @@ -162,10 +162,6 @@ public void onFrameAvailable(@NonNull SurfaceTexture texture) { // mNativeView==null. return; } - - if (listener != null) { - listener.onFrameAvailable(); - } markTextureFrameAvailable(id); } }; @@ -211,7 +207,7 @@ protected void finalize() throws Throwable { } @Override - public void setImageFrameListener(@Nullable ImageFrameListener listener) { + public void setOnFrameConsumedListener(@Nullable OnFrameConsumedListener listener) { this.listener = listener; } } diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java index c362ee9e9e35e..655d0db865b4e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java @@ -27,6 +27,14 @@ public SurfaceTextureWrapper(@NonNull SurfaceTexture surfaceTexture) { this(surfaceTexture, null); } + /** + * A wrapper for a SurfaceTexture. + * + *

The provided {@code onFrameConsumed} callback must be invoked when the most recent image was + * consumed. + * + * @param onFrameConsumed The callback after the {@code updateTexImage} is called. + */ public SurfaceTextureWrapper( @NonNull SurfaceTexture surfaceTexture, @Nullable Runnable onFrameConsumed) { this.surfaceTexture = surfaceTexture; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 871a0a5f754a9..4cbc514cb7ced 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -57,21 +57,24 @@ class PlatformViewWrapper extends FrameLayout { @Nullable private TextureRegistry.SurfaceTextureEntry textureEntry; private final AtomicLong pendingFramesCount = new AtomicLong(0L); - private final TextureRegistry.ImageFrameListener frameListener = - new TextureRegistry.ImageFrameListener() { - @Override - public void onFrameAvailable() {} - + private final TextureRegistry.OnFrameConsumedListener listener = + new TextureRegistry.OnFrameConsumedListener() { @Override public void onFrameConsumed() { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + if (Build.VERSION.SDK_INT == 29) { pendingFramesCount.decrementAndGet(); } } }; + private void onFrameProduced() { + if (Build.VERSION.SDK_INT == 29) { + pendingFramesCount.incrementAndGet(); + } + } + private boolean shouldDrawToSurfaceNow() { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + if (Build.VERSION.SDK_INT == 29) { return pendingFramesCount.get() <= 0L; } return true; @@ -86,7 +89,7 @@ public PlatformViewWrapper( @NonNull Context context, @NonNull TextureRegistry.SurfaceTextureEntry textureEntry) { this(context); this.textureEntry = textureEntry; - textureEntry.setImageFrameListener(frameListener); + textureEntry.setOnFrameConsumedListener(listener); setTexture(textureEntry.surfaceTexture()); } @@ -141,10 +144,7 @@ public void setTexture(@Nullable SurfaceTexture newTx) { } else { canvas.drawColor(Color.TRANSPARENT); } - - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - pendingFramesCount.incrementAndGet(); - } + onFrameProduced(); } finally { surface.unlockCanvasAndPost(canvas); } @@ -257,10 +257,7 @@ public void draw(Canvas canvas) { surfaceCanvas.drawColor(Color.TRANSPARENT); } super.draw(surfaceCanvas); - - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - pendingFramesCount.incrementAndGet(); - } + onFrameProduced(); } finally { surface.unlockCanvasAndPost(surfaceCanvas); } diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 772277b0cc0fd..0db6344997802 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -37,7 +37,6 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.UiThread; import io.flutter.Log; @@ -887,20 +886,10 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur private final long id; private final SurfaceTextureWrapper textureWrapper; private boolean released; - @Nullable private ImageFrameListener listener; - private final Runnable onFrameConsumed = - new Runnable() { - @Override - public void run() { - if (listener != null) { - listener.onFrameConsumed(); - } - } - }; SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture) { this.id = id; - this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture, onFrameConsumed); + this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // The callback relies on being executed on the UI thread (unsynchronised read of @@ -929,9 +918,6 @@ public void onFrameAvailable(SurfaceTexture texture) { return; } - if (listener != null) { - listener.onFrameAvailable(); - } mNativeView .getFlutterJNI() .markTextureFrameAvailable(SurfaceTextureRegistryEntry.this.id); @@ -970,10 +956,5 @@ public void release() { textureWrapper.release(); mNativeView.getFlutterJNI().unregisterTexture(id); } - - @Override - public void setImageFrameListener(@Nullable ImageFrameListener listener) { - this.listener = listener; - } } } diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index 205799ffab516..ed890f0d38b3f 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -43,15 +43,12 @@ interface SurfaceTextureEntry { /** Deregisters and releases this SurfaceTexture. */ void release(); - /** Set a listener that will be notified when a image frame state changes. */ - default void setImageFrameListener(@Nullable ImageFrameListener listener) {} + /** Set a listener that will be notified when the most recent image has been consumed. */ + default void setOnFrameConsumedListener(@Nullable OnFrameConsumedListener listener) {} } - /** Listener invoked when a new image frame becomes available and has been consumed. */ - interface ImageFrameListener { - /** This method will to be invoked when a new image frame becomes available. */ - void onFrameAvailable(); - + /** Listener invoked when the most recent image has been consumed. */ + interface OnFrameConsumedListener { /** * This method will to be invoked when the most recent image from the image stream has been * consumed. diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 0ce34da7296ad..110a8c9bb5ee8 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -321,11 +321,8 @@ public void itNotifyImageFrameListener() { FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI); AtomicInteger invocationCount = new AtomicInteger(0); - final TextureRegistry.ImageFrameListener listener = - new TextureRegistry.ImageFrameListener() { - @Override - public void onFrameAvailable() {} - + final TextureRegistry.OnFrameConsumedListener listener = + new TextureRegistry.OnFrameConsumedListener() { @Override public void onFrameConsumed() { invocationCount.incrementAndGet(); @@ -334,7 +331,7 @@ public void onFrameConsumed() { FlutterRenderer.SurfaceTextureRegistryEntry entry = (FlutterRenderer.SurfaceTextureRegistryEntry) flutterRenderer.createSurfaceTexture(); - entry.setImageFrameListener(listener); + entry.setOnFrameConsumedListener(listener); // Execute the behavior under test. entry.textureWrapper().updateTexImage();