From b1ebaf5065e07f05da00c89e686e2218ebd60979 Mon Sep 17 00:00:00 2001 From: Amir Hardon Date: Wed, 11 Jul 2018 15:34:45 -0700 Subject: [PATCH] Implement PlatformViewsController. Each platform view created (by a plugin supplied factory) is attached to a virtual display. The virtual displays are controlled by VirtualDisplayController objects. The PlatformViewsController maintains a mapping from a platform view's id to its VirtualDisplayController, which allows it to operate on the virtual display for a given platform view ID when asked so over the method channel. This is using API level 20 APIs, on lower API levels all platform views method channel calls are noops. We can make this work on API 19 with some refactoring to the TextureRegistry (allow the engine Java code to recycle a texture entry id). This CL also adds a platform view id parameter to the PlatformViewFactory#create() method. This allows plugins to route platform channel messages to specific instances of a platform view. TBD in future CLs: * Forward touch events to the platform views. * Support accessibility for platform views. flutter/flutter#19030 --- shell/platform/android/BUILD.gn | 2 + .../plugin/platform/PlatformViewFactory.java | 8 +- .../platform/PlatformViewsController.java | 147 ++++++++++++++++-- .../platform/SingleViewPresentation.java | 61 ++++++++ .../platform/VirtualDisplayController.java | 97 ++++++++++++ travis/licenses_golden/licenses_flutter | 7 +- 6 files changed, 299 insertions(+), 23 deletions(-) create mode 100644 shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java create mode 100644 shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 817c1c29250f8..b922bf23170f3 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -129,6 +129,8 @@ java_library("flutter_shell_java") { "io/flutter/plugin/platform/PlatformViewRegistry.java", "io/flutter/plugin/platform/PlatformViewRegistryImpl.java", "io/flutter/plugin/platform/PlatformViewsController.java", + "io/flutter/plugin/platform/SingleViewPresentation.java", + "io/flutter/plugin/platform/VirtualDisplayController.java", "io/flutter/util/PathUtils.java", "io/flutter/util/Preconditions.java", "io/flutter/view/AccessibilityBridge.java", diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java index ac70b1776d2af..6fa1756e0c061 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java @@ -7,5 +7,11 @@ import android.content.Context; public interface PlatformViewFactory { - PlatformView create(Context context); + /** + * Creates a new Android view to be embedded in the Flutter hierarchy. + * + * @param context the context to be used when creating the view, this is different than FlutterView's context. + * @param viewId unique identifier for the created instance, this value is known on the Dart side. + */ + PlatformView create(Context context, String viewId); } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index f3afbb8b3d082..3b5752e56b332 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -4,11 +4,17 @@ package io.flutter.plugin.platform; +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.Log; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.StandardMethodCodec; import io.flutter.view.FlutterView; +import io.flutter.view.TextureRegistry; +import java.util.HashMap; import java.util.Map; /** @@ -18,14 +24,22 @@ * A platform views controller can be attached to at most one Flutter view. */ public class PlatformViewsController implements MethodChannel.MethodCallHandler { + private static final String TAG = "PlatformViewsController"; + private static final String CHANNEL_NAME = "flutter/platform_views"; + // API level 20 is required for VirtualDisplay#setSurface which we use when resizing a platform view. + private static final int MINIMAL_SDK = Build.VERSION_CODES.KITKAT_WATCH; + private final PlatformViewRegistryImpl mRegistry; private FlutterView mFlutterView; + private final HashMap vdControllers; + public PlatformViewsController() { mRegistry = new PlatformViewRegistryImpl(); + vdControllers = new HashMap<>(); } public void attachFlutterView(FlutterView view) { @@ -49,48 +63,147 @@ public PlatformViewRegistry getRegistry() { } public void onFlutterViewDestroyed() { - // TODO(amirh): tear down all vd resources. + for (int id : vdControllers.keySet()) { + Log.d("AMIR", "disposing: " + id); + VirtualDisplayController controller = vdControllers.get(id); + controller.dispose(); + } + vdControllers.clear(); } @Override - public void onMethodCall(MethodCall call, MethodChannel.Result result) { + public void onMethodCall(final MethodCall call, final MethodChannel.Result result) { + if (Build.VERSION.SDK_INT < MINIMAL_SDK) { + Log.e(TAG, "Trying to use platform views with API " + Build.VERSION.SDK_INT + + ", required API level is: " + MINIMAL_SDK); + return; + } switch (call.method) { case "create": - createPlatformView(call); - break; + mFlutterView.post(new Runnable() { + @Override + public void run() { + createPlatformView(call, result); + } + }); + return; case "dispose": - disposePlatformView(call); - break; + mFlutterView.post(new Runnable() { + @Override + public void run() { + disposePlatformView(call, result); + } + }); + return; case "resize": - resizePlatformView(call); - break; + mFlutterView.post(new Runnable() { + @Override + public void run() { + resizePlatformView(call, result); + } + }); + return; } result.success(null); } - private void createPlatformView(MethodCall call) { + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) + private void createPlatformView(MethodCall call, MethodChannel.Result result) { Map args = call.arguments(); int id = (int) args.get("id"); String viewType = (String) args.get("viewType"); - double width = (double) args.get("width"); - double height = (double) args.get("height"); + double logicalWidth = (double) args.get("width"); + double logicalHeight = (double) args.get("height"); + + if (vdControllers.containsKey(id)) { + result.error( + "error", + "Trying to create an already created platform view, view id: " + id, + null + ); + return; + } + + Context context = mFlutterView.getContext(); + + PlatformViewFactory viewFactory = mRegistry.getFactory(viewType); + if (viewFactory == null) { + result.error( + "error", + "Trying to create a platform view of unregistered type: " + viewType, + null + ); + return; + } - // TODO(amirh): implement this. + TextureRegistry.SurfaceTextureEntry textureEntry = mFlutterView.createSurfaceTexture(); + VirtualDisplayController vdController = VirtualDisplayController.create( + mFlutterView.getContext(), + viewFactory, + textureEntry.surfaceTexture(), + toPhysicalPixels(logicalWidth), + toPhysicalPixels(logicalHeight), + Integer.toString(id) + ); + + if (vdController == null) { + result.error( + "error", + "Failed creating virtual display for a " + viewType + " with id: " + id, + null + ); + return; + } + + vdControllers.put(id, vdController); + + // TODO(amirh): copy accessibility nodes to the FlutterView's accessibility tree. + + result.success(textureEntry.id()); } - private void disposePlatformView(MethodCall call) { - int id = (int) call.arguments(); + private void disposePlatformView(MethodCall call, MethodChannel.Result result) { + int id = call.arguments(); - // TODO(amirh): implement this. + VirtualDisplayController vdController = vdControllers.get(id); + if (vdController == null) { + result.error( + "error", + "Trying to dispose a platform view with unknown id: " + id, + null + ); + return; + } + + vdController.dispose(); + vdControllers.remove(id); } - private void resizePlatformView(MethodCall call) { + private void resizePlatformView(MethodCall call, MethodChannel.Result result) { Map args = call.arguments(); int id = (int) args.get("id"); double width = (double) args.get("width"); double height = (double) args.get("height"); - // TODO(amirh): implement this. + VirtualDisplayController vdController = vdControllers.get(id); + if (vdController == null) { + result.error( + "error", + "Trying to resize a platform view with unknown id: " + id, + null + ); + return; + } + vdController.resize( + toPhysicalPixels(width), + toPhysicalPixels(height) + ); + result.success(null); + } + + private int toPhysicalPixels(double logicalPixels) { + float density = mFlutterView.getContext().getResources().getDisplayMetrics().density; + return (int) Math.round(logicalPixels * density); } } diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java new file mode 100644 index 0000000000000..d0131fec23fc4 --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -0,0 +1,61 @@ +package io.flutter.plugin.platform; + +import android.annotation.TargetApi; +import android.app.Presentation; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.view.Display; +import android.view.View; +import android.widget.FrameLayout; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) +class SingleViewPresentation extends Presentation { + private final PlatformViewFactory mViewFactory; + + private View mView; + private String mViewId; + + // As the root view of a display cannot be detached, we use this mContainer + // as the root, and attach mView to it. This allows us to detach mView. + private FrameLayout mContainer; + + /** + * Creates a presentation that will use the view factory to create a new + * platform view in the presentation's onCreate, and attach it. + */ + public SingleViewPresentation(Context outerContext, Display display, PlatformViewFactory viewFactory, String viewId) { + super(outerContext, display); + mViewFactory = viewFactory; + mViewId = viewId; + } + + /** + * Creates a presentation that will attach an already existing view as + * its root view. + * + *

The display's density must match the density of the context used + * when the view was created. + */ + public SingleViewPresentation(Context outerContext, Display display, View view) { + super(outerContext, display); + mViewFactory = null; + mView = view; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (mView == null) { + mView = mViewFactory.create(getContext(), mViewId).getView(); + } + mContainer = new FrameLayout(getContext()); + mContainer.addView(mView); + setContentView(mContainer); + } + + public View detachView() { + mContainer.removeView(mView); + return mView; + } +} diff --git a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java new file mode 100644 index 0000000000000..d18fc0b75d983 --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java @@ -0,0 +1,97 @@ +package io.flutter.plugin.platform; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; +import android.os.Build; +import android.view.Surface; +import android.view.View; + +@TargetApi(Build.VERSION_CODES.KITKAT_WATCH) +class VirtualDisplayController { + + public static VirtualDisplayController create( + Context context, + PlatformViewFactory viewFactory, + SurfaceTexture surfaceTexture, + int width, + int height, + String viewId + ) { + surfaceTexture.setDefaultBufferSize(width, height); + Surface surface = new Surface(surfaceTexture); + DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + + int densityDpi = context.getResources().getDisplayMetrics().densityDpi; + VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay( + "flutter-vd", + width, + height, + densityDpi, + surface, + 0 + ); + + if (virtualDisplay == null) { + return null; + } + + return new VirtualDisplayController(context, virtualDisplay, viewFactory, surface, surfaceTexture, viewId); + } + + private final Context mContext; + private final int mDensityDpi; + private final SurfaceTexture mSurfaceTexture; + private VirtualDisplay mVirtualDisplay; + private SingleViewPresentation mPresentation; + private Surface mSurface; + + + VirtualDisplayController( + Context context, + VirtualDisplay virtualDisplay, + PlatformViewFactory viewFactory, + Surface surface, + SurfaceTexture surfaceTexture, + String viewId + ) { + mSurfaceTexture = surfaceTexture; + mSurface = surface; + mContext = context; + mVirtualDisplay = virtualDisplay; + mDensityDpi = context.getResources().getDisplayMetrics().densityDpi; + mPresentation = new SingleViewPresentation(context, mVirtualDisplay.getDisplay(), viewFactory, viewId); + mPresentation.show(); + } + + public void resize(int width, int height) { + View view = mPresentation.detachView(); + mPresentation.hide(); + // 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. + mVirtualDisplay.setSurface(null); + mVirtualDisplay.release(); + + mSurfaceTexture.setDefaultBufferSize(width, height); + DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); + mVirtualDisplay = displayManager.createVirtualDisplay( + "flutter-vd", + width, + height, + mDensityDpi, + mSurface, + 0 + ); + mPresentation = new SingleViewPresentation(mContext, mVirtualDisplay.getDisplay(), view); + mPresentation.show(); + } + + public void dispose() { + mVirtualDisplay.release(); + } +} diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index 2c95c62bc39b3..6e20484676185 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -202,11 +202,8 @@ FILE: ../../../flutter/lib/ui/window/window.h FILE: ../../../flutter/shell/common/skia_event_tracer_impl.cc FILE: ../../../flutter/shell/platform/android/apk_asset_provider.cc FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/JSONUtil.java -FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformView.java -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/PlatformViewsController.java +FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java FILE: ../../../flutter/shell/platform/darwin/desktop/Info.plist FILE: ../../../flutter/shell/platform/darwin/ios/framework/Flutter.podspec FILE: ../../../flutter/shell/platform/darwin/ios/framework/Info.plist