diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 817c1c29250f8c0cc1f2a149a852119317543641..b922bf23170f3c67b4a10ec10a3a771343dab7be 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/PlatformView.java b/shell/platform/android/io/flutter/plugin/platform/PlatformView.java index 9b60d1fdbf4ea252829b4bf491864d0fa2996427..6e379e96a6ba0214d228ff6eaf482d6cdd2026d3 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformView.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformView.java @@ -14,4 +14,14 @@ public interface PlatformView { * Returns the Android view to be embedded in the Flutter hierarchy. */ View getView(); + + /** + * Dispose this platform view. + * + *

The {@link PlatformView} object is unusable after this method is called. + * + *

Plugins implementing {@link PlatformView} must clear all references to the View object and the PlatformView + * after this method is called. Failing to do so will result in a memory leak. + */ + void dispose(); } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java index ac70b1776d2af116d98488b87c19de6b3f550622..9cb7731e1ccb63677d779cf8738c0d4c88a5cdca 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 @@ package io.flutter.plugin.platform; 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, int viewId); } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 2d10a45d81fbfb74cb8b95486657a3f64b56cba4..77051ee37087f37f0f73f6de91ae593adef3ad19 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -4,11 +4,15 @@ 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; @@ -20,14 +24,22 @@ import java.util.Map; * 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) { @@ -51,48 +63,129 @@ public class PlatformViewsController implements MethodChannel.MethodCallHandler } public void onFlutterViewDestroyed() { - // TODO(amirh): tear down all vd resources. + for (VirtualDisplayController controller : vdControllers.values()) { + 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; + createPlatformView(call, result); + return; case "dispose": - disposePlatformView(call); - break; + disposePlatformView(call, result); + return; case "resize": - resizePlatformView(call); - break; + resizePlatformView(call, result); + return; } - result.success(null); + result.notImplemented(); } - 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; + } - // TODO(amirh): implement this. + PlatformViewFactory viewFactory = mRegistry.getFactory(viewType); + if (viewFactory == null) { + result.error( + "error", + "Trying to create a platform view of unregistered type: " + viewType, + null + ); + return; + } + + TextureRegistry.SurfaceTextureEntry textureEntry = mFlutterView.createSurfaceTexture(); + VirtualDisplayController vdController = VirtualDisplayController.create( + mFlutterView.getContext(), + viewFactory, + textureEntry.surfaceTexture(), + toPhysicalPixels(logicalWidth), + toPhysicalPixels(logicalHeight), + 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(); + + VirtualDisplayController vdController = vdControllers.get(id); + if (vdController == null) { + result.error( + "error", + "Trying to dispose a platform view with unknown id: " + id, + null + ); + return; + } - // TODO(amirh): implement this. + vdController.dispose(); + vdControllers.remove(id); + result.success(null); } - 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 0000000000000000000000000000000000000000..72bbca92a928c09b7aef0d1a91ea0d8f7ff415a7 --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -0,0 +1,64 @@ +// Copyright 2018 The Chromium 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.annotation.TargetApi; +import android.app.Presentation; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.view.Display; +import android.widget.FrameLayout; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) +class SingleViewPresentation extends Presentation { + private final PlatformViewFactory mViewFactory; + + private PlatformView mView; + private int 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, int 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, PlatformView 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); + } + mContainer = new FrameLayout(getContext()); + mContainer.addView(mView.getView()); + setContentView(mContainer); + } + + public PlatformView detachView() { + mContainer.removeView(mView.getView()); + 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 0000000000000000000000000000000000000000..5514d4334e091ea8118037ffc3e4355230291d4f --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java @@ -0,0 +1,102 @@ +// Copyright 2018 The Chromium 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.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, + int 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; + + + private VirtualDisplayController( + Context context, + VirtualDisplay virtualDisplay, + PlatformViewFactory viewFactory, + Surface surface, + SurfaceTexture surfaceTexture, + int 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) { + PlatformView 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() { + mPresentation.detachView().dispose(); + mVirtualDisplay.release(); + } +} diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index 29035a43eb7aefc2b05780124600a8a25cb05a10..3cdcba8d4ac5fc741b4bc927d93c6c1a45874201 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -501,6 +501,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor 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/ios/framework/Headers/FlutterCallbackCache.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm