mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add a PlatformViewRenderTarget abstraction (flutter/engine#43813)
- Introduce PlatformViewRenderTarget interface. - Refactor VirtualDisplayController and PlatformViewWrapper to extract SurfaceTexturePlatformViewRenderTarget into a separate class. In a future CL I will add an ImageReaderPlatformViewRenderTarget which will enable Platform Views on Impeller/Vulkan. Tracking issue: https://github.com/flutter/flutter/issues/130892
This commit is contained in:
parent
4cb94272fd
commit
b3945f7706
@ -2435,6 +2435,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platf
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistry.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistryImpl.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRenderTarget.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java + ../../../flutter/LICENSE
|
||||
@ -5136,10 +5137,12 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistry.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistryImpl.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRenderTarget.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTarget.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java
|
||||
|
||||
@ -297,10 +297,12 @@ android_java_sources = [
|
||||
"io/flutter/plugin/platform/PlatformViewFactory.java",
|
||||
"io/flutter/plugin/platform/PlatformViewRegistry.java",
|
||||
"io/flutter/plugin/platform/PlatformViewRegistryImpl.java",
|
||||
"io/flutter/plugin/platform/PlatformViewRenderTarget.java",
|
||||
"io/flutter/plugin/platform/PlatformViewWrapper.java",
|
||||
"io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java",
|
||||
"io/flutter/plugin/platform/PlatformViewsController.java",
|
||||
"io/flutter/plugin/platform/SingleViewPresentation.java",
|
||||
"io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTarget.java",
|
||||
"io/flutter/plugin/platform/VirtualDisplayController.java",
|
||||
"io/flutter/util/HandlerCompat.java",
|
||||
"io/flutter/util/PathUtils.java",
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
// 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 android.graphics.Canvas;
|
||||
import android.view.Surface;
|
||||
|
||||
/**
|
||||
* A PlatformViewRenderTarget interface allows an Android Platform View to be rendered into an
|
||||
* offscreen buffer (usually a texture is involved) that the engine can compose into the
|
||||
* FlutterView.
|
||||
*/
|
||||
public interface PlatformViewRenderTarget {
|
||||
// Called when the render target should be resized.
|
||||
public void resize(int width, int height);
|
||||
|
||||
// Returns the currently specified width.
|
||||
public int getWidth();
|
||||
|
||||
// Returns the currently specified height.
|
||||
public int getHeight();
|
||||
|
||||
// Forwards call to Surface returned by getSurface.
|
||||
// NOTE: If this returns null the RenderTarget is "full" and has no room for a
|
||||
// new frame.
|
||||
Canvas lockHardwareCanvas();
|
||||
|
||||
// Forwards call to Surface returned by getSurface.
|
||||
// NOTE: Must be called if lockHardwareCanvas returns a non-null Canvas.
|
||||
void unlockCanvasAndPost(Canvas canvas);
|
||||
|
||||
// The id of this render target.
|
||||
public long getId();
|
||||
|
||||
// Releases backing resources.
|
||||
public void release();
|
||||
|
||||
// Returns true in the case that backing resource have been released.
|
||||
public boolean isReleased();
|
||||
|
||||
// Returns the Surface to be rendered on to.
|
||||
public Surface getSurface();
|
||||
}
|
||||
@ -4,8 +4,6 @@
|
||||
|
||||
package io.flutter.plugin.platform;
|
||||
|
||||
import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
@ -14,10 +12,7 @@ import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.Build;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.view.ViewTreeObserver;
|
||||
@ -30,85 +25,30 @@ 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}.
|
||||
* Wraps a platform view to intercept gestures and project this view onto a {@link
|
||||
* PlatformViewRenderTarget}.
|
||||
*
|
||||
* <p>An Android platform view is composed by the engine using a {@code TextureLayer}. The view is
|
||||
* embeded to the Android view hierarchy like a normal view, but it's projected onto a {@link
|
||||
* SurfaceTexture}, so it can be efficiently composed by the engine.
|
||||
* PlatformViewRenderTarget}, so it can be efficiently composed by the engine.
|
||||
*
|
||||
* <p>Since the view is in the Android view hierarchy, keyboard and accessibility interactions
|
||||
* behave normally.
|
||||
*/
|
||||
@TargetApi(23)
|
||||
class PlatformViewWrapper extends FrameLayout {
|
||||
public class PlatformViewWrapper extends FrameLayout {
|
||||
private static final String TAG = "PlatformViewWrapper";
|
||||
|
||||
private int prevLeft;
|
||||
private int prevTop;
|
||||
private int left;
|
||||
private int top;
|
||||
private int bufferWidth;
|
||||
private int bufferHeight;
|
||||
private SurfaceTexture tx;
|
||||
private Surface surface;
|
||||
private AndroidTouchProcessor touchProcessor;
|
||||
private PlatformViewRenderTarget renderTarget;
|
||||
|
||||
@Nullable @VisibleForTesting ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener;
|
||||
private final AtomicLong pendingFramesCount = new AtomicLong(0L);
|
||||
|
||||
private final TextureRegistry.OnFrameConsumedListener frameConsumedListener =
|
||||
new TextureRegistry.OnFrameConsumedListener() {
|
||||
@Override
|
||||
public void onFrameConsumed() {
|
||||
if (Build.VERSION.SDK_INT == 29) {
|
||||
pendingFramesCount.decrementAndGet();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private boolean shouldRecreateSurfaceForLowMemory = false;
|
||||
private final TextureRegistry.OnTrimMemoryListener trimMemoryListener =
|
||||
new TextureRegistry.OnTrimMemoryListener() {
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
// When a memory pressure warning is received and the level equal {@code
|
||||
// ComponentCallbacks2.TRIM_MEMORY_COMPLETE}, the Android system releases the underlying
|
||||
// surface. If we continue to use the surface (e.g., call lockHardwareCanvas), a crash
|
||||
// occurs, and we found that this crash appeared on Android10 and above.
|
||||
// See https://github.com/flutter/flutter/issues/103870 for more details.
|
||||
//
|
||||
// Here our workaround is to recreate the surface before using it.
|
||||
if (level == TRIM_MEMORY_COMPLETE && Build.VERSION.SDK_INT >= 29) {
|
||||
shouldRecreateSurfaceForLowMemory = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void onFrameProduced() {
|
||||
if (Build.VERSION.SDK_INT == 29) {
|
||||
pendingFramesCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
private void recreateSurfaceIfNeeded() {
|
||||
if (shouldRecreateSurfaceForLowMemory) {
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
}
|
||||
surface = createSurface(tx);
|
||||
shouldRecreateSurfaceForLowMemory = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldDrawToSurfaceNow() {
|
||||
if (Build.VERSION.SDK_INT == 29) {
|
||||
return pendingFramesCount.get() <= 0L;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener;
|
||||
|
||||
public PlatformViewWrapper(@NonNull Context context) {
|
||||
super(context);
|
||||
@ -118,9 +58,13 @@ class PlatformViewWrapper extends FrameLayout {
|
||||
public PlatformViewWrapper(
|
||||
@NonNull Context context, @NonNull TextureRegistry.SurfaceTextureEntry textureEntry) {
|
||||
this(context);
|
||||
textureEntry.setOnFrameConsumedListener(frameConsumedListener);
|
||||
textureEntry.setOnTrimMemoryListener(trimMemoryListener);
|
||||
setTexture(textureEntry.surfaceTexture());
|
||||
this.renderTarget = new SurfaceTexturePlatformViewRenderTarget(textureEntry);
|
||||
}
|
||||
|
||||
public PlatformViewWrapper(
|
||||
@NonNull Context context, @NonNull PlatformViewRenderTarget renderTarget) {
|
||||
this(context);
|
||||
this.renderTarget = renderTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,62 +76,6 @@ class PlatformViewWrapper extends FrameLayout {
|
||||
touchProcessor = newTouchProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the texture where the view is projected onto.
|
||||
*
|
||||
* <p>{@link PlatformViewWrapper} doesn't take ownership of the {@link SurfaceTexture}. As a
|
||||
* result, the caller is responsible for releasing the texture.
|
||||
*
|
||||
* <p>{@link io.flutter.view.TextureRegistry} is responsible for creating and registering textures
|
||||
* in the engine. Therefore, the engine is responsible for also releasing the texture.
|
||||
*
|
||||
* @param newTx The texture where the view is projected onto.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void setTexture(@Nullable SurfaceTexture newTx) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Platform views cannot be displayed below API level 23. "
|
||||
+ "You can prevent this issue by setting `minSdkVersion: 23` in build.gradle.");
|
||||
return;
|
||||
}
|
||||
|
||||
tx = newTx;
|
||||
|
||||
if (bufferWidth > 0 && bufferHeight > 0) {
|
||||
tx.setDefaultBufferSize(bufferWidth, bufferHeight);
|
||||
}
|
||||
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
}
|
||||
surface = createSurface(newTx);
|
||||
|
||||
// Fill the entire canvas with a transparent color.
|
||||
// As a result, the background color of the platform view container is displayed
|
||||
// to the user until the platform view draws its first frame.
|
||||
final Canvas canvas = surface.lockHardwareCanvas();
|
||||
try {
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
onFrameProduced();
|
||||
} finally {
|
||||
surface.unlockCanvasAndPost(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
protected Surface createSurface(@NonNull SurfaceTexture tx) {
|
||||
return new Surface(tx);
|
||||
}
|
||||
|
||||
/** Returns the texture where the view is projected. */
|
||||
@Nullable
|
||||
public SurfaceTexture getTexture() {
|
||||
return tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the layout parameters for this view.
|
||||
*
|
||||
@ -200,37 +88,31 @@ class PlatformViewWrapper extends FrameLayout {
|
||||
top = params.topMargin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the image buffer.
|
||||
*
|
||||
* @param width The width of the screen buffer.
|
||||
* @param height The height of the screen buffer.
|
||||
*/
|
||||
public void setBufferSize(int width, int height) {
|
||||
bufferWidth = width;
|
||||
bufferHeight = height;
|
||||
if (tx != null) {
|
||||
tx.setDefaultBufferSize(width, height);
|
||||
public void resizeRenderTarget(int width, int height) {
|
||||
if (renderTarget != null) {
|
||||
renderTarget.resize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the image buffer width. */
|
||||
public int getBufferWidth() {
|
||||
return bufferWidth;
|
||||
public int getRenderTargetWidth() {
|
||||
if (renderTarget != null) {
|
||||
return renderTarget.getWidth();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Returns the image buffer height. */
|
||||
public int getBufferHeight() {
|
||||
return bufferHeight;
|
||||
public int getRenderTargetHeight() {
|
||||
if (renderTarget != null) {
|
||||
return renderTarget.getHeight();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Releases the surface. */
|
||||
/** Releases resources. */
|
||||
public void release() {
|
||||
// Don't release the texture.
|
||||
tx = null;
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
surface = null;
|
||||
if (renderTarget != null) {
|
||||
renderTarget.release();
|
||||
renderTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,42 +153,26 @@ class PlatformViewWrapper extends FrameLayout {
|
||||
@Override
|
||||
@SuppressLint("NewApi")
|
||||
public void draw(Canvas canvas) {
|
||||
if (surface == null) {
|
||||
if (renderTarget == null) {
|
||||
super.draw(canvas);
|
||||
Log.e(TAG, "Platform view cannot be composed without a surface.");
|
||||
Log.e(TAG, "Platform view cannot be composed without a RenderTarget.");
|
||||
return;
|
||||
}
|
||||
if (!surface.isValid()) {
|
||||
Log.e(TAG, "Invalid surface. The platform view cannot be displayed.");
|
||||
return;
|
||||
}
|
||||
if (tx == null || tx.isReleased()) {
|
||||
Log.e(TAG, "Invalid texture. The platform view cannot be displayed.");
|
||||
return;
|
||||
}
|
||||
// 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.
|
||||
final Canvas targetCanvas = renderTarget.lockHardwareCanvas();
|
||||
if (targetCanvas == null) {
|
||||
// Cannot render right now.
|
||||
invalidate();
|
||||
} else {
|
||||
// We try to recreate the surface before using it to avoid the crash:
|
||||
// https://github.com/flutter/flutter/issues/103870
|
||||
recreateSurfaceIfNeeded();
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
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.
|
||||
surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
super.draw(surfaceCanvas);
|
||||
onFrameProduced();
|
||||
} finally {
|
||||
surface.unlockCanvasAndPost(surfaceCanvas);
|
||||
}
|
||||
super.draw(targetCanvas);
|
||||
} finally {
|
||||
renderTarget.unlockCanvasAndPost(targetCanvas);
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,6 +204,11 @@ class PlatformViewWrapper extends FrameLayout {
|
||||
return touchProcessor.onTouchEvent(event, screenMatrix);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public ViewTreeObserver.OnGlobalFocusChangeListener getActiveFocusListener() {
|
||||
return this.activeFocusListener;
|
||||
}
|
||||
|
||||
public void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener userFocusListener) {
|
||||
unsetOnDescendantFocusChangeListener();
|
||||
final ViewTreeObserver observer = getViewTreeObserver();
|
||||
|
||||
@ -341,8 +341,8 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
context == null ? originalDisplayDensity : getDisplayDensity();
|
||||
onComplete.run(
|
||||
new PlatformViewsChannel.PlatformViewBufferSize(
|
||||
toLogicalPixels(vdController.getBufferWidth(), displayDensity),
|
||||
toLogicalPixels(vdController.getBufferHeight(), displayDensity)));
|
||||
toLogicalPixels(vdController.getRenderTargetWidth(), displayDensity),
|
||||
toLogicalPixels(vdController.getRenderTargetHeight(), displayDensity)));
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -361,9 +361,9 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
// Resizing the texture causes pixel stretching since the size of the GL texture used in
|
||||
// the engine
|
||||
// is set by the framework, but the texture buffer size is set by the platform down below.
|
||||
if (physicalWidth > viewWrapper.getBufferWidth()
|
||||
|| physicalHeight > viewWrapper.getBufferHeight()) {
|
||||
viewWrapper.setBufferSize(physicalWidth, physicalHeight);
|
||||
if (physicalWidth > viewWrapper.getRenderTargetWidth()
|
||||
|| physicalHeight > viewWrapper.getRenderTargetHeight()) {
|
||||
viewWrapper.resizeRenderTarget(physicalWidth, physicalHeight);
|
||||
}
|
||||
|
||||
final ViewGroup.LayoutParams viewWrapperLayoutParams = viewWrapper.getLayoutParams();
|
||||
@ -380,8 +380,8 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
}
|
||||
onComplete.run(
|
||||
new PlatformViewsChannel.PlatformViewBufferSize(
|
||||
toLogicalPixels(viewWrapper.getBufferWidth()),
|
||||
toLogicalPixels(viewWrapper.getBufferHeight())));
|
||||
toLogicalPixels(viewWrapper.getRenderTargetWidth()),
|
||||
toLogicalPixels(viewWrapper.getRenderTargetHeight())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -615,7 +615,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
textureId = textureEntry.id();
|
||||
}
|
||||
viewWrapper.setTouchProcessor(androidTouchProcessor);
|
||||
viewWrapper.setBufferSize(physicalWidth, physicalHeight);
|
||||
viewWrapper.resizeRenderTarget(physicalWidth, physicalHeight);
|
||||
|
||||
final FrameLayout.LayoutParams viewWrapperLayoutParams =
|
||||
new FrameLayout.LayoutParams(physicalWidth, physicalHeight);
|
||||
|
||||
@ -0,0 +1,179 @@
|
||||
package io.flutter.plugin.platform;
|
||||
|
||||
import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.Build;
|
||||
import android.view.Surface;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.view.TextureRegistry;
|
||||
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@TargetApi(26)
|
||||
public class SurfaceTexturePlatformViewRenderTarget implements PlatformViewRenderTarget {
|
||||
private static final String TAG = "SurfaceTexturePlatformViewRenderTarget";
|
||||
|
||||
private final AtomicLong pendingFramesCount = new AtomicLong(0L);
|
||||
|
||||
private void onFrameProduced() {
|
||||
if (Build.VERSION.SDK_INT == 29) {
|
||||
pendingFramesCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
private final SurfaceTextureEntry surfaceTextureEntry;
|
||||
|
||||
private SurfaceTexture surfaceTexture;
|
||||
private Surface surface;
|
||||
private int bufferWidth = 0;
|
||||
private int bufferHeight = 0;
|
||||
|
||||
private final TextureRegistry.OnFrameConsumedListener frameConsumedListener =
|
||||
new TextureRegistry.OnFrameConsumedListener() {
|
||||
@Override
|
||||
public void onFrameConsumed() {
|
||||
if (Build.VERSION.SDK_INT == 29) {
|
||||
pendingFramesCount.decrementAndGet();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private boolean shouldRecreateSurfaceForLowMemory = false;
|
||||
private final TextureRegistry.OnTrimMemoryListener trimMemoryListener =
|
||||
new TextureRegistry.OnTrimMemoryListener() {
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
// When a memory pressure warning is received and the level equal {@code
|
||||
// ComponentCallbacks2.TRIM_MEMORY_COMPLETE}, the Android system releases the
|
||||
// underlying
|
||||
// surface. If we continue to use the surface (e.g., call lockHardwareCanvas), a
|
||||
// crash
|
||||
// occurs, and we found that this crash appeared on Android10 and above.
|
||||
// See https://github.com/flutter/flutter/issues/103870 for more details.
|
||||
//
|
||||
// Here our workaround is to recreate the surface before using it.
|
||||
if (level == TRIM_MEMORY_COMPLETE && Build.VERSION.SDK_INT >= 29) {
|
||||
shouldRecreateSurfaceForLowMemory = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void recreateSurfaceIfNeeded() {
|
||||
if (!shouldRecreateSurfaceForLowMemory) {
|
||||
return;
|
||||
}
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
surface = null;
|
||||
}
|
||||
surface = createSurface();
|
||||
shouldRecreateSurfaceForLowMemory = false;
|
||||
}
|
||||
|
||||
protected Surface createSurface() {
|
||||
return new Surface(surfaceTexture);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (bufferWidth > 0 && bufferHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(bufferWidth, bufferHeight);
|
||||
}
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
surface = null;
|
||||
}
|
||||
surface = createSurface();
|
||||
|
||||
// Fill the entire canvas with a transparent color.
|
||||
// As a result, the background color of the platform view container is displayed
|
||||
// to the user until the platform view draws its first frame.
|
||||
final Canvas canvas = lockHardwareCanvas();
|
||||
try {
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
} finally {
|
||||
unlockCanvasAndPost(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of PlatformViewRenderTarget */
|
||||
public SurfaceTexturePlatformViewRenderTarget(SurfaceTextureEntry surfaceTextureEntry) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Platform views cannot be displayed below API level 23"
|
||||
+ "You can prevent this issue by setting `minSdkVersion: 23` in build.gradle.");
|
||||
}
|
||||
this.surfaceTextureEntry = surfaceTextureEntry;
|
||||
this.surfaceTexture = surfaceTextureEntry.surfaceTexture();
|
||||
surfaceTextureEntry.setOnFrameConsumedListener(frameConsumedListener);
|
||||
surfaceTextureEntry.setOnTrimMemoryListener(trimMemoryListener);
|
||||
init();
|
||||
}
|
||||
|
||||
public Canvas lockHardwareCanvas() {
|
||||
recreateSurfaceIfNeeded();
|
||||
|
||||
// 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 (Build.VERSION.SDK_INT == 29 && pendingFramesCount.get() > 0L) {
|
||||
return null;
|
||||
}
|
||||
if (surfaceTexture == null || surfaceTexture.isReleased()) {
|
||||
Log.e(TAG, "Invalid RenderTarget: null or already released SurfaceTexture");
|
||||
return null;
|
||||
}
|
||||
onFrameProduced();
|
||||
return surface.lockHardwareCanvas();
|
||||
}
|
||||
|
||||
public void unlockCanvasAndPost(Canvas canvas) {
|
||||
surface.unlockCanvasAndPost(canvas);
|
||||
}
|
||||
|
||||
public void resize(int width, int height) {
|
||||
bufferWidth = width;
|
||||
bufferHeight = height;
|
||||
if (surfaceTexture != null) {
|
||||
surfaceTexture.setDefaultBufferSize(bufferWidth, bufferHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return bufferWidth;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return bufferHeight;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return this.surfaceTextureEntry.id();
|
||||
}
|
||||
|
||||
public boolean isReleased() {
|
||||
return surfaceTexture == null;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
// Don't release the texture.
|
||||
surfaceTexture = null;
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
surface = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Surface getSurface() {
|
||||
recreateSurfaceIfNeeded();
|
||||
return surface;
|
||||
}
|
||||
}
|
||||
@ -12,8 +12,8 @@ import android.hardware.display.DisplayManager;
|
||||
import android.hardware.display.VirtualDisplay;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.View.OnFocusChangeListener;
|
||||
import android.view.ViewTreeObserver;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@ -33,12 +33,16 @@ class VirtualDisplayController {
|
||||
int viewId,
|
||||
Object createParams,
|
||||
OnFocusChangeListener focusChangeListener) {
|
||||
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
if (width == 0 || height == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DisplayManager displayManager =
|
||||
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
|
||||
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
final PlatformViewRenderTarget renderTarget =
|
||||
new SurfaceTexturePlatformViewRenderTarget(textureEntry);
|
||||
|
||||
// Virtual Display crashes for some PlatformViews if the width or height is bigger
|
||||
// than the physical screen size. We have tried to clamp or scale down the size to prevent
|
||||
// the crash, but both solutions lead to unwanted behavior because the
|
||||
@ -49,14 +53,15 @@ class VirtualDisplayController {
|
||||
// TODO(cyanglaz): find a way to prevent the crash without introducing size mistach betewen
|
||||
// virtual display and AndroidPlatformView widget.
|
||||
// https://github.com/flutter/flutter/issues/93115
|
||||
textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
|
||||
Surface surface = new Surface(textureEntry.surfaceTexture());
|
||||
DisplayManager displayManager =
|
||||
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
|
||||
|
||||
int densityDpi = context.getResources().getDisplayMetrics().densityDpi;
|
||||
renderTarget.resize(width, height);
|
||||
VirtualDisplay virtualDisplay =
|
||||
displayManager.createVirtualDisplay("flutter-vd", width, height, densityDpi, surface, 0);
|
||||
displayManager.createVirtualDisplay(
|
||||
"flutter-vd#" + viewId,
|
||||
width,
|
||||
height,
|
||||
metrics.densityDpi,
|
||||
renderTarget.getSurface(),
|
||||
0);
|
||||
|
||||
if (virtualDisplay == null) {
|
||||
return null;
|
||||
@ -67,13 +72,11 @@ class VirtualDisplayController {
|
||||
accessibilityEventsDelegate,
|
||||
virtualDisplay,
|
||||
view,
|
||||
surface,
|
||||
renderTarget,
|
||||
textureEntry,
|
||||
focusChangeListener,
|
||||
viewId,
|
||||
createParams);
|
||||
controller.bufferWidth = width;
|
||||
controller.bufferHeight = height;
|
||||
return controller;
|
||||
}
|
||||
|
||||
@ -82,31 +85,31 @@ class VirtualDisplayController {
|
||||
private final Context context;
|
||||
private final AccessibilityEventsDelegate accessibilityEventsDelegate;
|
||||
private final int densityDpi;
|
||||
private final int viewId;
|
||||
private final TextureRegistry.SurfaceTextureEntry textureEntry;
|
||||
private final PlatformViewRenderTarget renderTarget;
|
||||
private final OnFocusChangeListener focusChangeListener;
|
||||
private final Surface surface;
|
||||
|
||||
private VirtualDisplay virtualDisplay;
|
||||
private int bufferWidth;
|
||||
private int bufferHeight;
|
||||
|
||||
private VirtualDisplayController(
|
||||
Context context,
|
||||
AccessibilityEventsDelegate accessibilityEventsDelegate,
|
||||
VirtualDisplay virtualDisplay,
|
||||
PlatformView view,
|
||||
Surface surface,
|
||||
PlatformViewRenderTarget renderTarget,
|
||||
TextureRegistry.SurfaceTextureEntry textureEntry,
|
||||
OnFocusChangeListener focusChangeListener,
|
||||
int viewId,
|
||||
Object createParams) {
|
||||
this.context = context;
|
||||
this.accessibilityEventsDelegate = accessibilityEventsDelegate;
|
||||
this.renderTarget = renderTarget;
|
||||
this.textureEntry = textureEntry;
|
||||
this.focusChangeListener = focusChangeListener;
|
||||
this.surface = surface;
|
||||
this.viewId = viewId;
|
||||
this.virtualDisplay = virtualDisplay;
|
||||
densityDpi = context.getResources().getDisplayMetrics().densityDpi;
|
||||
this.densityDpi = context.getResources().getDisplayMetrics().densityDpi;
|
||||
presentation =
|
||||
new SingleViewPresentation(
|
||||
context,
|
||||
@ -118,33 +121,33 @@ class VirtualDisplayController {
|
||||
presentation.show();
|
||||
}
|
||||
|
||||
public int getBufferWidth() {
|
||||
return bufferWidth;
|
||||
public int getRenderTargetWidth() {
|
||||
if (renderTarget != null) {
|
||||
return renderTarget.getWidth();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getBufferHeight() {
|
||||
return bufferHeight;
|
||||
public int getRenderTargetHeight() {
|
||||
if (renderTarget != null) {
|
||||
return renderTarget.getHeight();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable) {
|
||||
boolean isFocused = getView().isFocused();
|
||||
final SingleViewPresentation.PresentationState presentationState = presentation.detachState();
|
||||
// We detach the surface to prevent it being destroyed when releasing the vd.
|
||||
//
|
||||
// setSurface is only available starting API 20. We could support API 19 by re-creating a new
|
||||
// SurfaceTexture here. This will require refactoring the TextureRegistry to allow recycling
|
||||
// texture
|
||||
// entry IDs.
|
||||
virtualDisplay.setSurface(null);
|
||||
virtualDisplay.release();
|
||||
|
||||
bufferWidth = width;
|
||||
bufferHeight = height;
|
||||
textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
|
||||
DisplayManager displayManager =
|
||||
final DisplayManager displayManager =
|
||||
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
|
||||
renderTarget.resize(width, height);
|
||||
virtualDisplay =
|
||||
displayManager.createVirtualDisplay("flutter-vd", width, height, densityDpi, surface, 0);
|
||||
displayManager.createVirtualDisplay(
|
||||
"flutter-vd#" + viewId, width, height, densityDpi, renderTarget.getSurface(), 0);
|
||||
|
||||
final View embeddedView = getView();
|
||||
// There's a bug in Android version older than O where view tree observer onDrawListeners don't
|
||||
|
||||
@ -4,19 +4,17 @@ import static android.view.View.OnFocusChangeListener;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.View.OnFocusChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
@ -42,83 +40,6 @@ public class PlatformViewWrapperTest {
|
||||
verify(wrapper, times(1)).invalidate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTexture_writesToBuffer() {
|
||||
final Surface surface = mock(Surface.class);
|
||||
final PlatformViewWrapper wrapper =
|
||||
new PlatformViewWrapper(ctx) {
|
||||
@Override
|
||||
protected Surface createSurface(@NonNull SurfaceTexture tx) {
|
||||
return surface;
|
||||
}
|
||||
};
|
||||
|
||||
final SurfaceTexture tx = mock(SurfaceTexture.class);
|
||||
when(tx.isReleased()).thenReturn(false);
|
||||
|
||||
final Canvas canvas = mock(Canvas.class);
|
||||
when(surface.lockHardwareCanvas()).thenReturn(canvas);
|
||||
|
||||
// Test.
|
||||
wrapper.setTexture(tx);
|
||||
|
||||
// Verify.
|
||||
verify(surface, times(1)).lockHardwareCanvas();
|
||||
verify(surface, times(1)).unlockCanvasAndPost(canvas);
|
||||
verify(canvas, times(1)).drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
verifyNoMoreInteractions(surface);
|
||||
verifyNoMoreInteractions(canvas);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void draw_writesToBuffer() {
|
||||
final Surface surface = mock(Surface.class);
|
||||
final PlatformViewWrapper wrapper =
|
||||
new PlatformViewWrapper(ctx) {
|
||||
@Override
|
||||
protected Surface createSurface(@NonNull SurfaceTexture tx) {
|
||||
return surface;
|
||||
}
|
||||
};
|
||||
|
||||
wrapper.addView(
|
||||
new View(ctx) {
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
canvas.drawColor(Color.RED);
|
||||
}
|
||||
});
|
||||
|
||||
final int size = 100;
|
||||
wrapper.measure(size, size);
|
||||
wrapper.layout(0, 0, size, size);
|
||||
|
||||
final SurfaceTexture tx = mock(SurfaceTexture.class);
|
||||
when(tx.isReleased()).thenReturn(false);
|
||||
|
||||
when(surface.lockHardwareCanvas()).thenReturn(mock(Canvas.class));
|
||||
|
||||
wrapper.setTexture(tx);
|
||||
|
||||
reset(surface);
|
||||
|
||||
final Canvas canvas = mock(Canvas.class);
|
||||
when(surface.lockHardwareCanvas()).thenReturn(canvas);
|
||||
when(surface.isValid()).thenReturn(true);
|
||||
|
||||
// Test.
|
||||
wrapper.invalidate();
|
||||
wrapper.draw(new Canvas());
|
||||
|
||||
// Verify.
|
||||
verify(canvas, times(1)).drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
verify(surface, times(1)).isValid();
|
||||
verify(surface, times(1)).lockHardwareCanvas();
|
||||
verify(surface, times(1)).unlockCanvasAndPost(canvas);
|
||||
verifyNoMoreInteractions(surface);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(
|
||||
shadows = {
|
||||
@ -140,36 +61,6 @@ public class PlatformViewWrapperTest {
|
||||
verify(canvas, times(1)).drawColor(Color.RED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release() {
|
||||
final Surface surface = mock(Surface.class);
|
||||
final PlatformViewWrapper wrapper =
|
||||
new PlatformViewWrapper(ctx) {
|
||||
@Override
|
||||
protected Surface createSurface(@NonNull SurfaceTexture tx) {
|
||||
return surface;
|
||||
}
|
||||
};
|
||||
|
||||
final SurfaceTexture tx = mock(SurfaceTexture.class);
|
||||
when(tx.isReleased()).thenReturn(false);
|
||||
|
||||
final Canvas canvas = mock(Canvas.class);
|
||||
when(surface.lockHardwareCanvas()).thenReturn(canvas);
|
||||
|
||||
wrapper.setTexture(tx);
|
||||
reset(surface);
|
||||
reset(tx);
|
||||
|
||||
// Test.
|
||||
wrapper.release();
|
||||
|
||||
// Verify.
|
||||
verify(surface, times(1)).release();
|
||||
verifyNoMoreInteractions(surface);
|
||||
verifyNoMoreInteractions(tx);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void focusChangeListener_hasFocus() {
|
||||
final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
|
||||
@ -255,16 +146,16 @@ public class PlatformViewWrapperTest {
|
||||
}
|
||||
};
|
||||
|
||||
assertNull(view.activeFocusListener);
|
||||
assertNull(view.getActiveFocusListener());
|
||||
|
||||
view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class));
|
||||
assertNotNull(view.activeFocusListener);
|
||||
assertNotNull(view.getActiveFocusListener());
|
||||
|
||||
final ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener =
|
||||
view.activeFocusListener;
|
||||
view.getActiveFocusListener();
|
||||
|
||||
view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class));
|
||||
assertNotNull(view.activeFocusListener);
|
||||
assertNotNull(view.getActiveFocusListener());
|
||||
|
||||
verify(viewTreeObserver, times(1)).removeOnGlobalFocusChangeListener(activeFocusListener);
|
||||
}
|
||||
@ -282,16 +173,16 @@ public class PlatformViewWrapperTest {
|
||||
}
|
||||
};
|
||||
|
||||
assertNull(view.activeFocusListener);
|
||||
assertNull(view.getActiveFocusListener());
|
||||
|
||||
view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class));
|
||||
assertNotNull(view.activeFocusListener);
|
||||
assertNotNull(view.getActiveFocusListener());
|
||||
|
||||
final ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener =
|
||||
view.activeFocusListener;
|
||||
view.getActiveFocusListener();
|
||||
|
||||
view.unsetOnDescendantFocusChangeListener();
|
||||
assertNull(view.activeFocusListener);
|
||||
assertNull(view.getActiveFocusListener());
|
||||
|
||||
view.unsetOnDescendantFocusChangeListener();
|
||||
verify(viewTreeObserver, times(1)).removeOnGlobalFocusChangeListener(activeFocusListener);
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
package io.flutter.plugin.platform;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@TargetApi(31)
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SurfaceTexturePlatformViewRenderTargetTest {
|
||||
private final Context ctx = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void create_clearsTexture() {
|
||||
final Canvas canvas = mock(Canvas.class);
|
||||
final Surface surface = mock(Surface.class);
|
||||
when(surface.lockHardwareCanvas()).thenReturn(canvas);
|
||||
when(surface.isValid()).thenReturn(true);
|
||||
final SurfaceTexture surfaceTexture = mock(SurfaceTexture.class);
|
||||
final SurfaceTextureEntry surfaceTextureEntry = mock(SurfaceTextureEntry.class);
|
||||
when(surfaceTextureEntry.surfaceTexture()).thenReturn(surfaceTexture);
|
||||
when(surfaceTexture.isReleased()).thenReturn(false);
|
||||
|
||||
// Test.
|
||||
final SurfaceTexturePlatformViewRenderTarget renderTarget =
|
||||
new SurfaceTexturePlatformViewRenderTarget(surfaceTextureEntry) {
|
||||
@Override
|
||||
protected Surface createSurface() {
|
||||
return surface;
|
||||
}
|
||||
};
|
||||
|
||||
// Verify.
|
||||
verify(surface, times(1)).lockHardwareCanvas();
|
||||
verify(surface, times(1)).unlockCanvasAndPost(canvas);
|
||||
verify(canvas, times(1)).drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
verifyNoMoreInteractions(surface);
|
||||
verifyNoMoreInteractions(canvas);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void viewDraw_writesToBuffer() {
|
||||
final Canvas canvas = mock(Canvas.class);
|
||||
final Surface surface = mock(Surface.class);
|
||||
when(surface.lockHardwareCanvas()).thenReturn(canvas);
|
||||
when(surface.isValid()).thenReturn(true);
|
||||
final SurfaceTexture surfaceTexture = mock(SurfaceTexture.class);
|
||||
final SurfaceTextureEntry surfaceTextureEntry = mock(SurfaceTextureEntry.class);
|
||||
when(surfaceTextureEntry.surfaceTexture()).thenReturn(surfaceTexture);
|
||||
when(surfaceTexture.isReleased()).thenReturn(false);
|
||||
|
||||
final SurfaceTexturePlatformViewRenderTarget renderTarget =
|
||||
new SurfaceTexturePlatformViewRenderTarget(surfaceTextureEntry) {
|
||||
@Override
|
||||
protected Surface createSurface() {
|
||||
return surface;
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
// Test.
|
||||
final Canvas c = renderTarget.lockHardwareCanvas();
|
||||
platformView.draw(c);
|
||||
renderTarget.unlockCanvasAndPost(c);
|
||||
|
||||
// Verify.
|
||||
verify(canvas, times(1)).drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
verify(canvas, times(1)).drawColor(Color.RED);
|
||||
verify(surface, times(2)).lockHardwareCanvas();
|
||||
verify(surface, times(2)).unlockCanvasAndPost(canvas);
|
||||
verifyNoMoreInteractions(surface);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void release() {
|
||||
final Canvas canvas = mock(Canvas.class);
|
||||
final Surface surface = mock(Surface.class);
|
||||
when(surface.lockHardwareCanvas()).thenReturn(canvas);
|
||||
when(surface.isValid()).thenReturn(true);
|
||||
final SurfaceTexture surfaceTexture = mock(SurfaceTexture.class);
|
||||
final SurfaceTextureEntry surfaceTextureEntry = mock(SurfaceTextureEntry.class);
|
||||
when(surfaceTextureEntry.surfaceTexture()).thenReturn(surfaceTexture);
|
||||
when(surfaceTexture.isReleased()).thenReturn(false);
|
||||
final SurfaceTexturePlatformViewRenderTarget renderTarget =
|
||||
new SurfaceTexturePlatformViewRenderTarget(surfaceTextureEntry) {
|
||||
@Override
|
||||
protected Surface createSurface() {
|
||||
return surface;
|
||||
}
|
||||
};
|
||||
|
||||
reset(surface);
|
||||
reset(surfaceTexture);
|
||||
|
||||
// Test.
|
||||
renderTarget.release();
|
||||
|
||||
// Verify.
|
||||
verify(surface, times(1)).release();
|
||||
verifyNoMoreInteractions(surface);
|
||||
verifyNoMoreInteractions(surfaceTexture);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user