Allow Image backed platform view rendering target on Android >= 29 again (flutter/engine#46958)

- Refactor the fence waiting code to only wait on Android >= 33.
- Log a warning message once per image rendering target on Android >= 29 && < 33.
- Add a simple unit test of ImageReaderPlatformViewRenderTargets.
This commit is contained in:
John McCutchan
2023-10-16 13:08:13 -07:00
committed by GitHub
parent 0174442f71
commit 9f4cf7153a
8 changed files with 177 additions and 19 deletions

View File

@@ -20,7 +20,6 @@ import androidx.annotation.VisibleForTesting;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.view.TextureRegistry;
import io.flutter.view.TextureRegistry.ImageTextureEntry;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
@@ -343,6 +342,7 @@ public class FlutterRenderer implements TextureRegistry {
private static final String TAG = "ImageTextureRegistryEntry";
private final long id;
private boolean released;
private boolean ignoringFence = false;
private Image image;
ImageTextureRegistryEntry(long id) {
@@ -390,27 +390,45 @@ public class FlutterRenderer implements TextureRegistry {
}
}
@Override
@TargetApi(33)
private void waitOnFence(Image image) {
try {
SyncFence fence = image.getFence();
boolean signaled = fence.awaitForever();
if (!signaled) {
Log.e(TAG, "acquireLatestImage image's fence was never signalled.");
}
} catch (IOException e) {
// Drop.
}
}
@TargetApi(29)
private void maybeWaitOnFence(Image image) {
if (image == null) {
return;
}
if (Build.VERSION.SDK_INT >= 33) {
// The fence API is only available on Android >= 33.
waitOnFence(image);
return;
}
if (!ignoringFence) {
// Log once per ImageTextureEntry.
ignoringFence = true;
Log.w(TAG, "ImageTextureEntry can't wait on the fence on Android < 33");
}
}
@Override
@TargetApi(29)
public Image acquireLatestImage() {
Image r;
synchronized (this) {
r = this.image;
this.image = null;
}
if (r != null) {
try {
SyncFence fence = r.getFence();
if (fence.getSignalTime() == SyncFence.SIGNAL_TIME_PENDING) {
boolean signaled = fence.awaitForever();
if (!signaled) {
Log.e(TAG, "acquireLatestImage image's fence was never signalled.");
}
}
} catch (IOException e) {
// Drop.
}
}
maybeWaitOnFence(r);
return r;
}

View File

@@ -12,7 +12,7 @@ import android.view.Surface;
import io.flutter.Log;
import io.flutter.view.TextureRegistry.ImageTextureEntry;
@TargetApi(33)
@TargetApi(29)
public class ImageReaderPlatformViewRenderTarget implements PlatformViewRenderTarget {
private ImageTextureEntry textureEntry;
private ImageReader reader;
@@ -72,18 +72,33 @@ public class ImageReaderPlatformViewRenderTarget implements PlatformViewRenderTa
return reader;
}
@TargetApi(29)
protected ImageReader createImageReader29() {
final ImageReader reader =
ImageReader.newInstance(
bufferWidth,
bufferHeight,
ImageFormat.PRIVATE,
MAX_IMAGES,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
reader.setOnImageAvailableListener(this.onImageAvailableListener, onImageAvailableHandler);
return reader;
}
protected ImageReader createImageReader() {
if (Build.VERSION.SDK_INT >= 33) {
return createImageReader33();
} else if (Build.VERSION.SDK_INT >= 29) {
return createImageReader29();
}
throw new UnsupportedOperationException(
"ImageReaderPlatformViewRenderTarget requires API version 33+");
"ImageReaderPlatformViewRenderTarget requires API version 29+");
}
public ImageReaderPlatformViewRenderTarget(ImageTextureEntry textureEntry) {
if (Build.VERSION.SDK_INT < 33) {
if (Build.VERSION.SDK_INT < 29) {
throw new UnsupportedOperationException(
"ImageReaderPlatformViewRenderTarget requires API version 33+");
"ImageReaderPlatformViewRenderTarget requires API version 29+");
}
this.textureEntry = textureEntry;
}

View File

@@ -0,0 +1,105 @@
// 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.
package io.flutter.plugin.platform;
import static android.os.Looper.getMainLooper;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.robolectric.Shadows.shadowOf;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.media.Image;
import android.view.View;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.view.TextureRegistry.ImageTextureEntry;
import org.junit.Test;
import org.junit.runner.RunWith;
@TargetApi(29)
@RunWith(AndroidJUnit4.class)
public class ImageReaderPlatformViewRenderTargetTest {
private final Context ctx = ApplicationProvider.getApplicationContext();
class TestImageTextureEntry implements ImageTextureEntry {
private Image lastPushedImage;
public long id() {
return 1;
}
public void release() {
if (this.lastPushedImage != null) {
this.lastPushedImage.close();
}
}
public void pushImage(Image image) {
if (this.lastPushedImage != null) {
this.lastPushedImage.close();
}
this.lastPushedImage = image;
}
public Image acquireLatestImage() {
Image r = this.lastPushedImage;
this.lastPushedImage = null;
return r;
}
}
@Test
public void viewDraw_writesToBuffer() {
final TestImageTextureEntry textureEntry = new TestImageTextureEntry();
final ImageReaderPlatformViewRenderTarget renderTarget =
new ImageReaderPlatformViewRenderTarget(textureEntry);
// Custom view.
final View platformView =
new View(ctx) {
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
canvas.drawColor(Color.RED);
}
};
final int size = 100;
platformView.measure(size, size);
platformView.layout(0, 0, size, size);
renderTarget.resize(size, size);
// We don't have an image in the texture entry.
assertNull(textureEntry.acquireLatestImage());
// Start rendering a frame.
final Canvas targetCanvas = renderTarget.lockHardwareCanvas();
assertNotNull(targetCanvas);
try {
// Fill the render target with transparent pixels. This is needed for platform views that
// expect a transparent background.
targetCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// Override the canvas that this subtree of views will use to draw.
platformView.draw(targetCanvas);
} finally {
// Finish rendering a frame.
renderTarget.unlockCanvasAndPost(targetCanvas);
}
// Pump the UI thread task loop. This is needed so that the OnImageAvailable callback
// gets invoked (resulting in textureEntry.pushImage being invoked).
shadowOf(getMainLooper()).idle();
// An image was pushed into the texture entry and it has the correct dimensions.
Image pushedImage = textureEntry.acquireLatestImage();
assertNotNull(pushedImage);
assertEquals(pushedImage.getWidth(), size);
assertEquals(pushedImage.getHeight(), size);
}
}

View File

@@ -1,3 +1,7 @@
// 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.
package io.flutter.plugin.platform;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;

View File

@@ -1,3 +1,7 @@
// 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.
package io.flutter.plugin.platform;
import static android.view.View.OnFocusChangeListener;

View File

@@ -1,3 +1,7 @@
// 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.
package io.flutter.plugin.platform;
import static android.os.Looper.getMainLooper;

View File

@@ -1,3 +1,7 @@
// 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.
package io.flutter.plugin.platform;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;

View File

@@ -1,3 +1,7 @@
// 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.
package io.flutter.plugin.platform;
import static org.junit.Assert.*;