Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Implement PlatformViewsController. #5722

Merged
merged 6 commits into from
Jul 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,14 @@ public interface PlatformView {
* Returns the Android view to be embedded in the Flutter hierarchy.
*/
View getView();

/**
* Dispose this platform view.
*
* <p>The {@link PlatformView} object is unusable after this method is called.
*
* <p>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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, int viewId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,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<Integer, VirtualDisplayController> vdControllers;

public PlatformViewsController() {
mRegistry = new PlatformViewRegistryImpl();
vdControllers = new HashMap<>();
}

public void attachFlutterView(FlutterView view) {
Expand All @@ -51,48 +63,129 @@ public PlatformViewRegistry getRegistry() {
}

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<String, Object> 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<String, Object> 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);
}

}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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;
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading