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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.*;
|
||||
|
||||
Reference in New Issue
Block a user